diff --git a/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..296fcf5c461ad4521fed496e376ed9289f50cf0c --- /dev/null +++ b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\AdminNotification\Ui\Component\DataProvider; + +use Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\SynchronizedFactory; + +/** + * Class DataProvider + */ +class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * DataProvider constructor. + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param SynchronizedFactory $messageCollectionFactory + * @param array $meta + * @param array $data + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + SynchronizedFactory $messageCollectionFactory, + array $meta = [], + array $data = [] + ) { + $this->collection = $messageCollectionFactory->create(); + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + } +} diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index 0a29908b77f6f4f6511fe5671702217ef95ceb90..527268df36b41efe5078528138342a03fa33745d 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -7,6 +7,7 @@ "magento/module-backend": "100.2.*", "magento/module-media-storage": "100.2.*", "magento/framework": "100.2.*", + "magento/module-ui": "100.2.*", "lib-libxml": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml index e3d32f17b6356bc974627b0daaba5dca5c467eea..780e48378a3a4069c29b50153eb6ca013f36147e 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml @@ -8,11 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="notifications"> - <block class="Magento\AdminNotification\Block\System\Messages" - name="system_messages" - as="system_messages" - before="-" - template="Magento_AdminNotification::system/messages.phtml"/> + <uiComponent name="notification_area"/> <block class="Magento\AdminNotification\Block\System\Messages\UnreadMessagePopup" name="unread_system_messages" as="unread_system_messages" diff --git a/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml new file mode 100644 index 0000000000000000000000000000000000000000..e0149fff714deaa5fc95910169ec92a26e4c673c --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">notification_area.notification_area_data_source</item> + <item name="deps" xsi:type="string">notification_area.notification_area_data_source</item> + </item> + <item name="spinner" xsi:type="string">columns</item> + </argument> + <dataSource name="notification_area_data_source"> + <argument name="dataProvider" xsi:type="configurableObject"> + <argument name="class" xsi:type="string">Magento\AdminNotification\Ui\Component\DataProvider\DataProvider</argument> + <argument name="name" xsi:type="string">notification_area_data_source</argument> + <argument name="primaryFieldName" xsi:type="string">identity</argument> + <argument name="requestFieldName" xsi:type="string">identity</argument> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item> + <item name="update_url" xsi:type="url" path="mui/index/render"/> + <item name="storageConfig" xsi:type="array"> + <item name="indexField" xsi:type="string">identity</item> + </item> + </item> + </argument> + </argument> + </dataSource> + <columns name="columns"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/listing</item> + <item name="template" xsi:type="string">Magento_AdminNotification/grid/listing</item> + </item> + </argument> + <column name="created_at"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/columns/message</item> + <item name="label" xsi:type="string" translate="true"/> + <item name="dataType" xsi:type="string">text</item> + <item name="sorting" xsi:type="string">asc</item> + <item name="sortOrder" xsi:type="number">30</item> + </item> + </argument> + </column> + </columns> +</listing> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js new file mode 100644 index 0000000000000000000000000000000000000000..aa5477ebafcf0df511981f2f874c7a1e4c7081bb --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js @@ -0,0 +1,46 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/grid/columns/column', + 'underscore' +], function (Column, _) { + 'use strict'; + + return Column.extend({ + defaults: { + bodyTmpl: 'Magento_AdminNotification/grid/cells/message', + messageIndex: 'text', + fieldClass: { + message: true, + 'message-warning': false, + 'message-progress': false, + 'message-success': false, + 'message-error': false + }, + statusMap: { + 0: 'info', + 1: 'progress', + 2: 'success', + 3: 'error' + } + }, + + /** @inheritdoc */ + getLabel: function (record) { + return record[this.messageIndex]; + }, + + /** @inheritdoc */ + getFieldClass: function ($row) { + var status = this.statusMap[$row.status] || 'warning', + result = {}; + + result['message-' + status] = true; + result = _.extend({}, this.fieldClass, result); + + return result; + } + }); +}); diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js new file mode 100644 index 0000000000000000000000000000000000000000..1fbda8d0196ca1fa9e1b58f40d0804892bf70891 --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/grid/listing', + 'Magento_Ui/js/lib/spinner', + 'jquery' +], function (Listing, loader, $) { + 'use strict'; + + return Listing.extend({ + defaults: { + imports: { + totalRecords: '${ $.provider }:data.totalRecords' + }, + selectors: { + collapsible: '.message-system-collapsible', + messages: '.message-system' + } + }, + + /** @inheritdoc */ + initObservable: function () { + this._super() + .track({ + totalRecords: 0 + }); + + return this; + }, + + /** @inheritdoc */ + showLoader: function () { + if (!this.source.firstLoad) { + this.fixLoaderHeight(); + this._super(); + } + }, + + /** + * Calculates loader height + * + * @param {Boolean} [closed] + */ + fixLoaderHeight: function (closed) { + var $messagesBlock = $(this.selectors.messages), + $collapsibleBlock = $(this.selectors.collapsible), + resultHeight = 0; + + if ($messagesBlock.length) { + resultHeight += $messagesBlock.outerHeight(); + } + + if ($collapsibleBlock.length && $collapsibleBlock.is(':visible') && !closed) { + resultHeight += $collapsibleBlock.outerHeight(); + } + + loader.get(this.name).height(resultHeight); + } + }); +}); diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html new file mode 100644 index 0000000000000000000000000000000000000000..869842e8ee87bcc9465d3763dc610c95361cad72 --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html @@ -0,0 +1,8 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div css="$col.getFieldClass($row())" + html="$col.getLabel($row())"/> \ No newline at end of file diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html new file mode 100644 index 0000000000000000000000000000000000000000..80f2f4f2423fbcad5ba9b8e030e56c806db5ef9a --- /dev/null +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html @@ -0,0 +1,32 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div id="system_messages" class="message-system" collapsible visible="totalRecords"> + <div class="message-system-inner" outerClick="fixLoaderHeight.bind($data, true)"> + <div class="message-system-short"> + <button class="message-system-action-dropdown" toggleCollapsible> + <span> + <translate args="'System Messages'"/>: + <text args="totalRecords"/> + </span> + </button> + <div class="message-system-short-wrapper" if="rows[0]" repeat="foreach: [rows[0]], item: '$row'" visible="!$collapsible.opened()"> + <fastForEach args="data: getVisible(), as: '$col'" > + <render args="$col.getBody()"/> + </fastForEach> + </div> + </div> + <div class="message-system-collapsible"> + <ul class="message-system-list"> + <li repeat="foreach: rows, item: '$row'"> + <fastForEach args="data: getVisible(), as: '$col'" > + <render args="$col.getBody()"/> + </fastForEach> + </li> + </ul> + </div> + </div> +</div> diff --git a/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php b/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php index 4025b2accbb178e7685947ccfdff9605e41fabf3..804f06ff667a3ce26144e60b8c3297d04faaf8ed 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php @@ -12,7 +12,6 @@ namespace Magento\Catalog\Api\Data; interface ProductAttributeInterface extends \Magento\Catalog\Api\Data\EavAttributeInterface { const ENTITY_TYPE_CODE = 'catalog_product'; - const CODE_TIER_PRICE_FIELD_PRICE = 'price'; const CODE_HAS_WEIGHT = 'product_has_weight'; const CODE_SPECIAL_PRICE = 'special_price'; const CODE_PRICE = 'price'; @@ -27,6 +26,9 @@ interface ProductAttributeInterface extends \Magento\Catalog\Api\Data\EavAttribu const CODE_COST = 'cost'; const CODE_SEO_FIELD_URL_KEY = 'url_key'; const CODE_TIER_PRICE = 'tier_price'; + const CODE_TIER_PRICE_FIELD_PRICE = 'price'; + const CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE = 'percentage_value'; + const CODE_TIER_PRICE_FIELD_VALUE_TYPE = 'value_type'; const CODE_SEO_FIELD_META_DESCRIPTION = 'meta_description'; const CODE_WEIGHT = 'weight'; } diff --git a/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php b/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php index d587aec01ccb6f062e6e5bdb4abe62a5031d519b..f02c3afa5aed9de2235e96648ae8de47b337d54f 100644 --- a/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php +++ b/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php @@ -8,6 +8,7 @@ namespace Magento\Catalog\Api; /** * @api + * @deprecated use ScopedProductTierPriceManagementInterface instead */ interface ProductTierPriceManagementInterface { diff --git a/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php b/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8a27480b776b643f86079af240fe179f33ac1573 --- /dev/null +++ b/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php @@ -0,0 +1,45 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Api; + +/** + * @api + */ +interface ScopedProductTierPriceManagementInterface +{ + /** + * Create tier price for product + * + * @param string $sku + * @param \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice + * @return boolean + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function add($sku, \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice); + + /** + * Remove tier price from product + * + * @param string $sku + * @param \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice + * @return boolean + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function remove($sku, \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice); + + /** + * Get tier price of product + * + * @param string $sku + * @param string $customerGroupId 'all' can be used to specify 'ALL GROUPS' + * @return \Magento\Catalog\Api\Data\ProductTierPriceInterface[] + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getList($sku, $customerGroupId); +} diff --git a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php index 9eb5e2e1fc770a8cb776e39b77d3a886d766b192..b994c787bee7aa76f8a4baa3648bcbb2c0e4ee27 100644 --- a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php +++ b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php @@ -5,12 +5,14 @@ */ namespace Magento\Catalog\Model\Config\Source\Product\Options; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; + /** * Price types mode source * * @author Magento Core Team <core@magentocommerce.com> */ -class Price implements \Magento\Framework\Option\ArrayInterface +class Price implements ProductPriceOptionsInterface { /** * {@inheritdoc} @@ -20,8 +22,8 @@ class Price implements \Magento\Framework\Option\ArrayInterface public function toOptionArray() { return [ - ['value' => 'fixed', 'label' => __('Fixed')], - ['value' => 'percent', 'label' => __('Percent')] + ['value' => self::VALUE_FIXED, 'label' => __('Fixed')], + ['value' => self::VALUE_PERCENT, 'label' => __('Percent')], ]; } } diff --git a/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php b/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d5d5062bad75832a5d712becbe67442fd7a10ac1 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Config\Source; + +use Magento\Framework\Data\OptionSourceInterface; + +/** + * Interface ProductPriceOptionsInterface + */ +interface ProductPriceOptionsInterface extends OptionSourceInterface +{ + /**#@+ + * Values + */ + const VALUE_FIXED = 'fixed'; + const VALUE_PERCENT = 'percent'; + /**#@-*/ +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index 720e491cef05e9de600c0db36ce4828ede4e6344..464adb27686d02882a91b1f318e8e998fdcd4a5c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -70,6 +70,11 @@ abstract class AbstractAction */ protected $_indexers; + /** + * @var \Magento\Catalog\Model\ResourceModel\Product + */ + private $productResource; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -213,12 +218,19 @@ abstract class AbstractAction $table = $this->_defaultIndexerResource->getTable('catalog_product_index_tier_price'); $this->_emptyTable($table); + $tierPriceExpression = $this->_connection->getCheckSql( + 'tp.value = 0', + 'product_price.value * (1 - tp.percentage_value / 100)', + 'tp.value' + ); $websiteExpression = $this->_connection->getCheckSql( 'tp.website_id = 0', - 'ROUND(tp.value * cwd.rate, 4)', - 'tp.value' + 'ROUND(' . $tierPriceExpression . ' * cwd.rate, 4)', + $tierPriceExpression ); $linkField = $this->getProductIdFieldName(); + $priceAttribute = $this->getProductResource()->getAttribute('price'); + $select = $this->_connection->select()->from( ['cpe' => $this->_defaultIndexerResource->getTable('catalog_product_entity')], ['cpe.entity_id'] @@ -238,8 +250,15 @@ abstract class AbstractAction ['cwd' => $this->_defaultIndexerResource->getTable('catalog_product_index_website')], 'cw.website_id = cwd.website_id', [] + )->join( + ['product_price' => $priceAttribute->getBackend()->getTable()], + 'tp.' . $linkField . ' = product_price.' . $linkField, + [] )->where( 'cw.website_id != 0' + )->where( + 'product_price.attribute_id = ?', + $priceAttribute->getAttributeId() )->columns( new \Zend_Db_Expr("MIN({$websiteExpression})") )->group( @@ -462,4 +481,17 @@ abstract class AbstractAction $indexList = $this->_connection->getIndexList($table); return $indexList[$this->_connection->getPrimaryKeyName($table)]['COLUMNS_LIST'][0]; } + + /** + * @return \Magento\Catalog\Model\ResourceModel\Product + * @deprecated + */ + private function getProductResource() + { + if (null === $this->productResource) { + $this->productResource = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\Model\ResourceModel\Product::class); + } + return $this->productResource; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index d104dc2ab687f076fb6bbd70ab2abcd65028d0df..3c5cef0e6ce78c10d7feea308ec622f201ff1b8c 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -129,6 +129,18 @@ abstract class AbstractGroupPrice extends Price return []; } + /** + * Get additional fields + * + * @param array $objectArray + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function getAdditionalFields($objectArray) + { + return []; + } + /** * Whether group price value fixed or percent of original price * @@ -176,9 +188,7 @@ abstract class AbstractGroupPrice extends Price throw new \Magento\Framework\Exception\LocalizedException(__($this->_getDuplicateErrorMessage())); } - if (!$this->isPositiveOrZero($priceRow['price'])) { - return __('Group price must be a number greater than 0.'); - } + $this->validatePrice($priceRow); $duplicates[$compare] = true; } @@ -228,6 +238,20 @@ abstract class AbstractGroupPrice extends Price return true; } + /** + * @param array $priceRow + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function validatePrice(array $priceRow) + { + if (!isset($priceRow['price']) || !$this->isPositiveOrZero($priceRow['price'])) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Group price must be a number greater than 0.') + ); + } + } + /** * Prepare group prices data for website * @@ -270,37 +294,70 @@ abstract class AbstractGroupPrice extends Price */ public function afterLoad($object) { - $storeId = $object->getStoreId(); + $data = $this->_getResource()->loadPriceData( + $object->getData($this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField()), + $this->getWebsiteId($object->getStoreId()) + ); + $this->setPriceData($object, $data); + + return $this; + } + + /** + * @param int $storeId + * @return int|null + */ + private function getWebsiteId($storeId) + { $websiteId = null; if ($this->getAttribute()->isScopeGlobal()) { $websiteId = 0; } elseif ($storeId) { $websiteId = $this->_storeManager->getStore($storeId)->getWebsiteId(); } + return $websiteId; + } - $data = $this->_getResource()->loadPriceData( - $object->getData($this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField()), - $websiteId - ); - foreach ($data as $k => $v) { - $data[$k]['website_price'] = $v['price']; - if ($v['all_groups']) { - $data[$k]['cust_group'] = $this->_groupManagement->getAllCustomersGroup()->getId(); - } - } - + /** + * @param \Magento\Catalog\Model\Product $object + * @param array $priceData + */ + public function setPriceData($object, $priceData) + { + $priceData = $this->modifyPriceData($object, $priceData); + $websiteId = $this->getWebsiteId($object->getStoreId()); if (!$object->getData('_edit_mode') && $websiteId) { - $data = $this->preparePriceData($data, $object->getTypeId(), $websiteId); + $priceData = $this->preparePriceData($priceData, $object->getTypeId(), $websiteId); } - $object->setData($this->getAttribute()->getName(), $data); - $object->setOrigData($this->getAttribute()->getName(), $data); + $object->setData($this->getAttribute()->getName(), $priceData); + $object->setOrigData($this->getAttribute()->getName(), $priceData); $valueChangedKey = $this->getAttribute()->getName() . '_changed'; $object->setOrigData($valueChangedKey, 0); $object->setData($valueChangedKey, 0); + } - return $this; + /** + * Perform price modification + * + * @param \Magento\Catalog\Model\Product $object + * @param array $data + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function modifyPriceData($object, $data) + { + /** @var array $priceItem */ + foreach ($data as $key => $priceItem) { + if (isset($priceItem['price']) && $priceItem['price'] > 0) { + $data[$key]['website_price'] = $priceItem['price']; + } + if ($priceItem['all_groups']) { + $data[$key]['cust_group'] = $this->_groupManagement->getAllCustomersGroup()->getId(); + } + } + return $data; } /** @@ -372,13 +429,13 @@ abstract class AbstractGroupPrice extends Price $useForAllGroups = $data['cust_group'] == $this->_groupManagement->getAllCustomersGroup()->getId(); $customerGroupId = !$useForAllGroups ? $data['cust_group'] : 0; - $new[$key] = array_merge( + $this->getAdditionalFields($data), [ 'website_id' => $data['website_id'], 'all_groups' => $useForAllGroups ? 1 : 0, 'customer_group_id' => $customerGroupId, - 'value' => $data['price'], + 'value' => isset($data['price']) ? $data['price'] : null, ], $this->_getAdditionalUniqueFields($data) ); @@ -412,14 +469,7 @@ abstract class AbstractGroupPrice extends Price } if (!empty($update)) { - foreach ($update as $k => $v) { - if ($old[$k]['price'] != $v['value']) { - $price = new \Magento\Framework\DataObject(['value_id' => $old[$k]['price_id'], 'value' => $v['value']]); - $this->_getResource()->savePriceData($price); - - $isChanged = true; - } - } + $isChanged = $this->updateValues($update, $old); } if ($isChanged) { @@ -430,6 +480,29 @@ abstract class AbstractGroupPrice extends Price return $this; } + /** + * @param array $valuesToUpdate + * @param array $oldValues + * @return boolean + */ + protected function updateValues(array $valuesToUpdate, array $oldValues) + { + $isChanged = false; + foreach ($valuesToUpdate as $key => $value) { + if ($oldValues[$key]['price'] != $value['value']) { + $price = new \Magento\Framework\DataObject( + [ + 'value_id' => $oldValues[$key]['price_id'], + 'value' => $value['value'] + ] + ); + $this->_getResource()->savePriceData($price); + $isChanged = true; + } + } + return $isChanged; + } + /** * Retrieve data for update attribute * diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php index fc0fc07d4d524db04afd4b18b304e1e074980634..480f8e8942e87f200ce3829641ea026d351b1967 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php @@ -75,6 +75,18 @@ class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPr return $uniqueFields; } + /** + * @inheritdoc + */ + protected function getAdditionalFields($objectArray) + { + $percentageValue = $this->getPercentage($objectArray); + return [ + 'value' => $percentageValue ? null : $objectArray['price'], + 'percentage_value' => $percentageValue ?: null, + ]; + } + /** * Error message when duplicates * @@ -105,4 +117,89 @@ class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPr { return false; } + + /** + * @inheritdoc + */ + public function validate($object) + { + $attribute = $this->getAttribute(); + $priceRows = $object->getData($attribute->getName()); + $priceRows = array_filter((array)$priceRows); + + foreach ($priceRows as $priceRow) { + $percentage = $this->getPercentage($priceRow); + if ($percentage !== null && (!$this->isPositiveOrZero($percentage) || $percentage > 100)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Percentage value must be a number between 0 and 100.') + ); + } + } + + return parent::validate($object); + } + + /** + * @inheritdoc + */ + protected function validatePrice(array $priceRow) + { + if (!$this->getPercentage($priceRow)) { + parent::validatePrice($priceRow); + } + } + + /** + * @inheritdoc + */ + protected function modifyPriceData($object, $data) + { + $data = parent::modifyPriceData($object, $data); + foreach ($data as $key => $tierPrice) { + if ($this->getPercentage($tierPrice)) { + $data[$key]['website_price'] = $object->getPrice() * (1 - $this->getPercentage($tierPrice) / 100); + } + } + return $data; + } + + /** + * @param array $valuesToUpdate + * @param array $oldValues + * @return boolean + */ + protected function updateValues(array $valuesToUpdate, array $oldValues) + { + $isChanged = false; + foreach ($valuesToUpdate as $key => $value) { + if ($oldValues[$key]['price'] != $value['value'] + || $this->getPercentage($oldValues[$key]) != $this->getPercentage($value) + ) { + $price = new \Magento\Framework\DataObject( + [ + 'value_id' => $oldValues[$key]['price_id'], + 'value' => $value['value'], + 'percentage_value' => $this->getPercentage($value) + ] + ); + $this->_getResource()->savePriceData($price); + + $isChanged = true; + } + } + return $isChanged; + } + + /** + * Check whether price has percentage value. + * + * @param array $priceRow + * @return null + */ + private function getPercentage($priceRow) + { + return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value']) + ? $priceRow['percentage_value'] + : null; + } } diff --git a/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php new file mode 100644 index 0000000000000000000000000000000000000000..f08702bdd73c426533e8656894bab2e161c2d5a3 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Product; + +use Magento\Catalog\Api\Data\ProductTierPriceInterface; +use Magento\Catalog\Api\ScopedProductTierPriceManagementInterface; +use Magento\Store\Model\ScopeInterface; + +class ScopedTierPriceManagement implements ScopedProductTierPriceManagementInterface +{ + /** + * @var \Magento\Catalog\Api\ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $config; + + /** + * @var PriceModifier + */ + private $priceModifier; + + /** + * @var TierPriceManagement + */ + private $tierPriceManagement; + + /** + * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\App\Config\ScopeConfigInterface $config + * @param PriceModifier $priceModifier + * @param TierPriceManagement $tierPriceManagement + */ + public function __construct( + \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\App\Config\ScopeConfigInterface $config, + PriceModifier $priceModifier, + TierPriceManagement $tierPriceManagement + ) { + $this->productRepository = $productRepository; + $this->storeManager = $storeManager; + $this->priceModifier = $priceModifier; + $this->config = $config; + $this->tierPriceManagement = $tierPriceManagement; + } + + /** + * {@inheritdoc} + */ + public function add($sku, ProductTierPriceInterface $tierPrice) + { + $product = $this->productRepository->get($sku, ['edit_mode' => true]); + $product->setTierPrices( + $this->prepareTierPrices($product->getTierPrices(), $tierPrice) + ); + try { + $this->productRepository->save($product); + } catch (\Exception $e) { + throw new \Magento\Framework\Exception\CouldNotSaveException(__('Could not save group price')); + } + return true; + } + + /** + * @param array $tierPrices + * @param ProductTierPriceInterface $tierPrice + * @return ProductTierPriceInterface[]|null + */ + private function prepareTierPrices(array $tierPrices, ProductTierPriceInterface $tierPrice) + { + $this->validate($tierPrice); + $websiteId = $this->getWebsiteId(); + + foreach ($tierPrices as $index => $item) { + $tierPriceWebsite = $tierPrice->getExtensionAttributes() + ? $tierPrice->getExtensionAttributes()->getWebsiteId() + : 0; + + if ($item->getCustomerGroupId() == $tierPrice->getCustomerGroupId() + && $websiteId == $tierPriceWebsite + && $item->getQty() == $tierPrice->getQty() + ) { + unset($tierPrices[$index]); + break; + } + } + + $tierPrices[] = $tierPrice; + return $tierPrices; + } + + /** + * @return int + */ + private function getWebsiteId() + { + $websiteIdentifier = 0; + $value = $this->config->getValue('catalog/price/scope', ScopeInterface::SCOPE_WEBSITE); + if ($value != 0) { + $websiteIdentifier = $this->storeManager->getWebsite()->getId(); + } + + return $websiteIdentifier; + } + + /** + * @param ProductTierPriceInterface $tierPrice + * @throws \Magento\Framework\Exception\InputException + * @return void + */ + private function validate(ProductTierPriceInterface $tierPrice) + { + $data = ['qty' => $tierPrice->getQty(), 'price' => $tierPrice->getValue()]; + foreach ($data as $value) { + if (!is_float($value) || $value <= 0) { + throw new \Magento\Framework\Exception\InputException(__('Please provide valid data')); + } + } + } + + /** + * {@inheritdoc} + */ + public function remove($sku, ProductTierPriceInterface $tierPrice) + { + $product = $this->productRepository->get($sku, ['edit_mode' => true]); + $this->priceModifier->removeTierPrice( + $product, + $tierPrice->getCustomerGroupId(), + $tierPrice->getQty(), + $this->getWebsiteId() + ); + return true; + } + + /** + * {@inheritdoc} + */ + public function getList($sku, $customerGroupId) + { + return $this->tierPriceManagement->getList($sku, $customerGroupId); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/TierPrice.php b/app/code/Magento/Catalog/Model/Product/TierPrice.php index 131280af1b1a428981d1c1f30c523a6dc940757d..e40b24631359c9874fe900bb171c13cd74a2e05d 100644 --- a/app/code/Magento/Catalog/Model/Product/TierPrice.php +++ b/app/code/Magento/Catalog/Model/Product/TierPrice.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ @@ -78,8 +77,6 @@ class TierPrice extends \Magento\Framework\Model\AbstractExtensibleModel impleme /** * {@inheritdoc} - * - * @return \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface|null */ public function getExtensionAttributes() { @@ -88,9 +85,6 @@ class TierPrice extends \Magento\Framework\Model\AbstractExtensibleModel impleme /** * {@inheritdoc} - * - * @param \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface $extensionAttributes - * @return $this */ public function setExtensionAttributes( \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface $extensionAttributes diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 20d25b9e5afcbc414f4abbe3af74328dce95770d..af15e049203f3f6aae27ef2c6ff071d49cdab8cd 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Product; use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Store\Model\Store; +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; /** * Product type price model @@ -80,6 +81,11 @@ class Price */ protected $config; + /** + * @var ProductTierPriceExtensionFactory + */ + private $tierPriceExtensionFactory; + /** * Price constructor. * @param \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory @@ -354,7 +360,8 @@ class Price $tierPrices = $this->getExistingPrices($product, 'tier_price'); foreach ($tierPrices as $price) { /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */ - $tierPrice = $this->tierPriceFactory->create(); + $tierPrice = $this->tierPriceFactory->create() + ->setExtensionAttributes($this->getTierPriceExtensionAttributes()); $tierPrice->setCustomerGroupId($price['cust_group']); if (array_key_exists('website_price', $price)) { $value = $price['website_price']; @@ -363,11 +370,29 @@ class Price } $tierPrice->setValue($value); $tierPrice->setQty($price['price_qty']); + if (isset($price['percentage_value'])) { + $tierPrice->getExtensionAttributes()->setPercentageValue($price['percentage_value']); + } + $websiteId = isset($price['website_id']) ? $price['website_id'] : $this->getWebsiteForPriceScope(); + $tierPrice->getExtensionAttributes()->setWebsiteId($websiteId); $prices[] = $tierPrice; } return $prices; } + /** + * @deprecated + * @return \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface + */ + private function getTierPriceExtensionAttributes() + { + if (!$this->tierPriceExtensionFactory) { + $this->tierPriceExtensionFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(ProductTierPriceExtensionFactory::class); + } + return $this->tierPriceExtensionFactory->create(); + } + /** * Sets list of product tier prices * @@ -382,19 +407,24 @@ class Price return $this; } - $websiteId = $this->getWebsiteForPriceScope(); $allGroupsId = $this->getAllCustomerGroupsId(); + $websiteId = $this->getWebsiteForPriceScope(); // build the new array of tier prices $prices = []; foreach ($tierPrices as $price) { + $extensionAttributes = $price->getExtensionAttributes(); + $websiteId = $extensionAttributes && $extensionAttributes->getWebsiteId() + ? $extensionAttributes->getWebsiteId() + : $websiteId; $prices[] = [ 'website_id' => $websiteId, 'cust_group' => $price->getCustomerGroupId(), 'website_price' => $price->getValue(), 'price' => $price->getValue(), 'all_groups' => ($price->getCustomerGroupId() == $allGroupsId), - 'price_qty' => $price->getQty() + 'price_qty' => $price->getQty(), + 'percentage_value' => $extensionAttributes ? $extensionAttributes->getPercentageValue() : null ]; } $product->setData('tier_price', $prices); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index 677d048de6a7f596014887971db414aad391614b..717056ddde02f1cff0bff978eb9bfb7652fa19e3 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -22,8 +22,21 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel */ public function loadPriceData($productId, $websiteId = null) { - $connection = $this->getConnection(); + $select = $this->getSelect($websiteId); + $productIdFieldName = $this->getProductIdFieldName(); + $select->where("{$productIdFieldName} = ?", $productId); + + $this->_loadPriceDataSelect($select); + return $this->getConnection()->fetchAll($select); + } + + /** + * @param int|null $websiteId + * @return \Magento\Framework\DB\Select + */ + public function getSelect($websiteId = null) + { $columns = [ 'price_id' => $this->getIdFieldName(), 'website_id' => 'website_id', @@ -34,12 +47,8 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel $columns = $this->_loadPriceDataColumns($columns); - $productIdFieldName = $this->getProductIdFieldName(); - $select = $connection->select() - ->from($this->getMainTable(), $columns) - ->where("{$productIdFieldName} = ?", $productId); - - $this->_loadPriceDataSelect($select); + $select = $this->getConnection()->select() + ->from($this->getMainTable(), $columns); if ($websiteId !== null) { if ($websiteId == '0') { @@ -48,8 +57,7 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel $select->where('website_id IN(?)', [0, $websiteId]); } } - - return $connection->fetchAll($select); + return $select; } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php index e75442637b9bb09cbcb8a235cd9ffb4abc415402..0f17ecbf98e4cec5b5fd8eebc84dea07da5dd9e5 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php @@ -34,6 +34,7 @@ class Tierprice extends AbstractGroupPrice { $columns = parent::_loadPriceDataColumns($columns); $columns['price_qty'] = 'qty'; + $columns['percentage_value'] = 'percentage_value'; return $columns; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index f53c1b29f8bd0d83680520cde33aea1bec1e310d..b2b20b9d223e223a093d7f6ae222e19631084528 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2090,12 +2090,13 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac if ($this->getFlag('tier_price_added')) { return $this; } + $linkField = $this->getConnection()->getAutoIncrementField($this->getTable('catalog_product_entity')); $tierPrices = []; $productIds = []; foreach ($this->getItems() as $item) { - $productIds[] = $item->getId(); - $tierPrices[$item->getId()] = []; + $productIds[] = $item->getData($linkField); + $tierPrices[$item->getData($linkField)] = []; } if (!$productIds) { return $this; @@ -2103,57 +2104,27 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ $attribute = $this->getAttribute('tier_price'); + /* @var $backend \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice */ + $backend = $attribute->getBackend(); $websiteId = 0; if (!$attribute->isScopeGlobal() && null !== $this->getStoreId()) { $websiteId = $this->_storeManager->getStore($this->getStoreId())->getWebsiteId(); } - $linkField = $this->getConnection()->getAutoIncrementField($this->getTable('catalog_product_entity')); - $connection = $this->getConnection(); - $columns = [ - 'price_id' => 'value_id', - 'website_id' => 'website_id', - 'all_groups' => 'all_groups', - 'cust_group' => 'customer_group_id', - 'price_qty' => 'qty', - 'price' => 'value', - 'product_id' => $linkField, - ]; - $select = $connection->select()->from( - $this->getTable('catalog_product_entity_tier_price'), - $columns - )->where( + $select = $backend->getResource()->getSelect($websiteId); + $select->columns(['product_id' => $linkField])->where( $linkField .' IN(?)', $productIds )->order( - [$linkField, 'qty'] + $linkField ); - if ($websiteId == 0) { - $select->where('website_id = ?', $websiteId); - } else { - $select->where('website_id IN(?)', [0, $websiteId]); - } - - foreach ($connection->fetchAll($select) as $row) { - $tierPrices[$row['product_id']][] = [ - 'website_id' => $row['website_id'], - 'cust_group' => $row['all_groups'] ? $this->_groupManagement->getAllCustomersGroup()->getId() : $row['cust_group'], - 'price_qty' => $row['price_qty'], - 'price' => $row['price'], - 'website_price' => $row['price'], - ]; + foreach ($this->getConnection()->fetchAll($select) as $row) { + $tierPrices[$row['product_id']][] = $row; } - /* @var $backend \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice */ - $backend = $attribute->getBackend(); - foreach ($this->getItems() as $item) { - $data = $tierPrices[$item->getId()]; - if (!empty($data) && $websiteId) { - $data = $backend->preparePriceData($data, $item->getTypeId(), $websiteId); - } - $item->setData('tier_price', $data); + $backend->setPriceData($item, $tierPrices[$item->getData($linkField)]); } $this->setFlag('tier_price_added', true); diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php index 03f3ac9581729b96eeab485da5be125072f23773..9683632f121b6af18a3397e53bac2943521683c9 100644 --- a/app/code/Magento/Catalog/Setup/UpgradeSchema.php +++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php @@ -32,6 +32,10 @@ class UpgradeSchema implements UpgradeSchemaInterface if (version_compare($context->getVersion(), '2.0.6', '<')) { $this->addUniqueKeyToCategoryProductTable($setup); } + + if (version_compare($context->getVersion(), '2.1.0', '<')) { + $this->addPercentageValueColumn($setup); + } $setup->endSetup(); } @@ -278,4 +282,25 @@ class UpgradeSchema implements UpgradeSchemaInterface $connection->dropColumn($setup->getTable($filedInfo['table']), $filedInfo['column']); } } + + /** + * Add percentage value column + * @param SchemaSetupInterface $setup + * @return void + */ + private function addPercentageValueColumn(SchemaSetupInterface $setup) + { + $connection = $setup->getConnection(); + $connection->addColumn( + $setup->getTable('catalog_product_entity_tier_price'), + 'percentage_value', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, + 'nullable' => true, + 'length' => '5,2', + 'comment' => 'Percentage value', + 'after' => 'value' + ] + ); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php index c51f9486c2ecb26c8fefb82ad3875c24b904af2d..5868e749446e391a987928314d3c133c1fc19537 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php @@ -8,11 +8,15 @@ namespace Magento\Catalog\Test\Unit\Model\Product\Type; +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Customer\Model\GroupManagement; /** * Price Test + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PriceTest extends \PHPUnit_Framework_TestCase { @@ -60,18 +64,18 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->objectManagerHelper = new ObjectManagerHelper($this); $this->product = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product::class); - $this->tpFactory = $this->getMockForAbstractClass( + $this->tpFactory = $this->getMock( \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class, + ['create'], [], '', false, - true, - true, - ['create'] + false, + false ); $this->websiteMock = $this->getMock(\Magento\Store\Model\Website::class, ['getId'], [], '', false); - $storeMangerMock = $this->getMockForAbstractClass( + $storeMangerMock = $this->getMockForAbstractClass( \Magento\Store\Model\StoreManagerInterface::class, [], '', @@ -84,7 +88,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase ->method('getWebsite') ->will($this->returnValue($this->websiteMock)); - $this->scopeConfigMock = $this->getMockForAbstractClass( + $this->scopeConfigMock = $this->getMockForAbstractClass( \Magento\Framework\App\Config\ScopeConfigInterface::class, [], '', @@ -94,7 +98,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase ['getValue'] ); - $group = $this->getMock( + $group = $this->getMock( \Magento\Customer\Model\Data\Group::class, [], [], @@ -107,7 +111,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->groupManagementMock->expects($this->any())->method('getAllCustomersGroup') ->will($this->returnValue($group)); - $this->model = $this->objectManagerHelper->getObject( + $this->model = $this->objectManagerHelper->getObject( \Magento\Catalog\Model\Product\Type\Price::class, [ 'tierPriceFactory' => $this->tpFactory, @@ -164,12 +168,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase public function testTierPrices($priceScope, $expectedWebsiteId) { // establish the behavior of the mocks - $this->scopeConfigMock->expects($this->any()) - ->method('getValue') - ->will($this->returnValue($priceScope)); - $this->websiteMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue($expectedWebsiteId)); + $this->scopeConfigMock->expects($this->any())->method('getValue')->will($this->returnValue($priceScope)); + $this->websiteMock->expects($this->any())->method('getId')->will($this->returnValue($expectedWebsiteId)); $this->tpFactory->expects($this->any()) ->method('create') ->will($this->returnCallback(function () { @@ -177,14 +177,21 @@ class PriceTest extends \PHPUnit_Framework_TestCase })); // create sample TierPrice objects that would be coming from a REST call + $tierPriceExtensionMock = $this->getMockBuilder(ProductTierPriceExtensionInterface::class) + ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue']) + ->getMock(); + $tierPriceExtensionMock->expects($this->any())->method('getWebsiteId')->willReturn($expectedWebsiteId); + $tierPriceExtensionMock->expects($this->any())->method('getPercentageValue')->willReturn(null); $tp1 = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product\TierPrice::class); $tp1->setValue(10); $tp1->setCustomerGroupId(1); $tp1->setQty(11); + $tp1->setExtensionAttributes($tierPriceExtensionMock); $tp2 = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product\TierPrice::class); $tp2->setValue(20); $tp2->setCustomerGroupId(2); $tp2->setQty(22); + $tp2->setExtensionAttributes($tierPriceExtensionMock); $tps = [$tp1, $tp2]; // force the product to have null tier prices @@ -213,11 +220,28 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->assertEquals($tps[$i]->getQty(), $tpData['price_qty'], 'Qty does not match'); } + $tierPriceExtention = $this->getMockBuilder(ProductTierPriceExtensionInterface::class) + ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue']) + ->getMock(); + $tierPriceExtention->expects($this->any())->method('getPercentageValue')->willReturn(50); + $tierPriceExtention->expects($this->any())->method('setWebsiteId'); + $factoryMock = $this->getMockBuilder(ProductTierPriceExtensionFactory::class)->setMethods(['create']) + ->disableOriginalConstructor()->getMock(); + $factoryMock->expects($this->any())->method('create')->willReturn($tierPriceExtention); + + $reflection = new \ReflectionClass(get_class($this->model)); + $reflectionProperty = $reflection->getProperty('tierPriceExtensionFactory'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->model, $factoryMock); + // test with the data retrieved as a REST object $tpRests = $this->model->getTierPrices($this->product); $this->assertNotNull($tpRests); $this->assertTrue(is_array($tpRests)); $this->assertEquals(sizeof($tps), sizeof($tpRests)); + foreach ($tpRests as $tpRest) { + $this->assertEquals(50, $tpRest->getExtensionAttributes()->getPercentageValue()); + } for ($i = 0; $i < sizeof($tps); $i++) { $this->assertEquals( diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index 3b92e54355fb66be4eb76cec2651ffbbab88434b..c42efd5a1e61fe1249c0cf28d53b8737d22f826d 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -7,9 +7,10 @@ namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\Locator\LocatorInterface; +use Magento\Customer\Model\Customer\Source\GroupSourceInterface; use Magento\Directory\Helper\Data; +use Magento\Framework\App\ObjectManager; use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Api\Data\GroupInterface; use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; @@ -81,6 +82,11 @@ class AdvancedPricing extends AbstractModifier */ protected $meta = []; + /** + * @var GroupSourceInterface + */ + private $customerGroupSource; + /** * @param LocatorInterface $locator * @param StoreManagerInterface $storeManager @@ -91,6 +97,7 @@ class AdvancedPricing extends AbstractModifier * @param Data $directoryHelper * @param ArrayManager $arrayManager * @param string $scopeName + * @param GroupSourceInterface $customerGroupSource * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -102,7 +109,8 @@ class AdvancedPricing extends AbstractModifier ModuleManager $moduleManager, Data $directoryHelper, ArrayManager $arrayManager, - $scopeName = '' + $scopeName = '', + GroupSourceInterface $customerGroupSource = null ) { $this->locator = $locator; $this->storeManager = $storeManager; @@ -113,6 +121,8 @@ class AdvancedPricing extends AbstractModifier $this->directoryHelper = $directoryHelper; $this->arrayManager = $arrayManager; $this->scopeName = $scopeName; + $this->customerGroupSource = $customerGroupSource + ?: ObjectManager::getInstance()->get(GroupSourceInterface::class); } /** @@ -174,7 +184,7 @@ class AdvancedPricing extends AbstractModifier * * @return $this */ - protected function customizeTierPrice() + private function customizeTierPrice() { $tierPricePath = $this->arrayManager->findPath( ProductAttributeInterface::CODE_TIER_PRICE, @@ -209,28 +219,13 @@ class AdvancedPricing extends AbstractModifier * * @return array */ - protected function getCustomerGroups() + private function getCustomerGroups() { if (!$this->moduleManager->isEnabled('Magento_Customer')) { return []; } - $customerGroups = [ - [ - 'label' => __('ALL GROUPS'), - 'value' => GroupInterface::CUST_GROUP_ALL, - ] - ]; - - /** @var GroupInterface[] $groups */ - $groups = $this->groupRepository->getList($this->searchCriteriaBuilder->create()); - foreach ($groups->getItems() as $group) { - $customerGroups[] = [ - 'label' => $group->getCode(), - 'value' => $group->getId(), - ]; - } - return $customerGroups; + return $this->customerGroupSource->toOptionArray(); } /** @@ -238,7 +233,7 @@ class AdvancedPricing extends AbstractModifier * * @return bool */ - protected function isScopeGlobal() + private function isScopeGlobal() { return $this->locator->getProduct() ->getResource() @@ -251,7 +246,7 @@ class AdvancedPricing extends AbstractModifier * * @return array */ - protected function getWebsites() + private function getWebsites() { $websites = [ [ @@ -292,7 +287,7 @@ class AdvancedPricing extends AbstractModifier * * @return int */ - protected function getDefaultCustomerGroup() + private function getDefaultCustomerGroup() { return $this->groupManagement->getAllCustomersGroup()->getId(); } @@ -316,7 +311,7 @@ class AdvancedPricing extends AbstractModifier * * @return bool */ - protected function isShowWebsiteColumn() + private function isShowWebsiteColumn() { if ($this->isScopeGlobal() || $this->storeManager->isSingleStoreMode()) { return false; @@ -329,7 +324,7 @@ class AdvancedPricing extends AbstractModifier * * @return bool */ - protected function isMultiWebsites() + private function isMultiWebsites() { return !$this->storeManager->isSingleStoreMode(); } @@ -339,7 +334,7 @@ class AdvancedPricing extends AbstractModifier * * @return bool */ - protected function isAllowChangeWebsite() + private function isAllowChangeWebsite() { if (!$this->isShowWebsiteColumn() || $this->locator->getProduct()->getStoreId()) { return false; @@ -352,7 +347,7 @@ class AdvancedPricing extends AbstractModifier * * @return $this */ - protected function addAdvancedPriceLink() + private function addAdvancedPriceLink() { $pricePath = $this->arrayManager->findPath( ProductAttributeInterface::CODE_PRICE, @@ -405,7 +400,7 @@ class AdvancedPricing extends AbstractModifier * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function getTierPriceStructure($tierPricePath) + private function getTierPriceStructure($tierPricePath) { return [ 'arguments' => [ @@ -452,6 +447,7 @@ class AdvancedPricing extends AbstractModifier 'value' => $this->getDefaultWebsite(), 'visible' => $this->isMultiWebsites(), 'disabled' => ($this->isShowWebsiteColumn() && !$this->isAllowChangeWebsite()), + 'sortOrder' => 10, ], ], ], @@ -467,6 +463,7 @@ class AdvancedPricing extends AbstractModifier 'label' => __('Customer Group'), 'options' => $this->getCustomerGroups(), 'value' => $this->getDefaultCustomerGroup(), + 'sortOrder' => 20, ], ], ], @@ -480,6 +477,7 @@ class AdvancedPricing extends AbstractModifier 'dataType' => Number::NAME, 'label' => __('Quantity'), 'dataScope' => 'price_qty', + 'sortOrder' => 30, 'validation' => [ 'required-entry' => true, 'validate-greater-than-zero' => true, @@ -494,6 +492,7 @@ class AdvancedPricing extends AbstractModifier 'data' => [ 'config' => [ 'componentType' => Field::NAME, + 'component' => 'Magento_Catalog/js/form/element/price-input', 'formElement' => Input::NAME, 'dataType' => Price::NAME, 'label' => __('Price'), @@ -502,11 +501,15 @@ class AdvancedPricing extends AbstractModifier 'addbefore' => $this->locator->getStore() ->getBaseCurrency() ->getCurrencySymbol(), + 'sortOrder' => 40, 'validation' => [ 'required-entry' => true, 'validate-greater-than-zero' => true, 'validate-number' => true, ], + 'imports' => [ + 'priceValue' => '${ $.provider }:data.product.price', + ], ], ], ], @@ -518,6 +521,7 @@ class AdvancedPricing extends AbstractModifier 'componentType' => 'actionDelete', 'dataType' => Text::NAME, 'label' => '', + 'sortOrder' => 50, ], ], ], @@ -533,7 +537,7 @@ class AdvancedPricing extends AbstractModifier * * @return $this */ - protected function specialPriceDataToInline() + private function specialPriceDataToInline() { $pathFrom = $this->arrayManager->findPath('special_from_date', $this->meta, null, 'children'); $pathTo = $this->arrayManager->findPath('special_to_date', $this->meta, null, 'children'); @@ -589,7 +593,7 @@ class AdvancedPricing extends AbstractModifier * * @return $this */ - protected function customizeAdvancedPricing() + private function customizeAdvancedPricing() { $this->meta['advanced-pricing']['arguments']['data']['config']['opened'] = true; $this->meta['advanced-pricing']['arguments']['data']['config']['collapsible'] = false; @@ -646,9 +650,9 @@ class AdvancedPricing extends AbstractModifier /** * Retrieve store * - * @return \Magento\Store\Model\Store + * @return \Magento\Store\Api\Data\StoreInterface */ - protected function getStore() + private function getStore() { return $this->locator->getStore(); } diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 36f072d3b0a15b358463a2715e5e214c30b51b24..49ad7d67d705899077d4beb13f4a26383982dcf5 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -46,6 +46,7 @@ <preference for="Magento\Catalog\Api\AttributeSetFinderInterface" type="Magento\Catalog\Model\Product\Attribute\AttributeSetFinder" /> <preference for="Magento\Catalog\Api\CategoryListInterface" type="Magento\Catalog\Model\CategoryList" /> <preference for="Magento\Catalog\Api\Data\CategorySearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> + <preference for="Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface" type="Magento\Catalog\Model\Config\Source\Product\Options\Price"/> <type name="Magento\Customer\Model\ResourceModel\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type> @@ -470,6 +471,7 @@ <preference for="Magento\Catalog\Api\ProductCustomOptionRepositoryInterface" type="\Magento\Catalog\Model\Product\Option\Repository" /> <preference for="Magento\Catalog\Api\Data\ProductCustomOptionTypeInterface" type="Magento\Catalog\Model\Product\Option\Type" /> <preference for="Magento\Catalog\Api\ProductTierPriceManagementInterface" type="\Magento\Catalog\Model\Product\TierPriceManagement" /> + <preference for="Magento\Catalog\Api\ScopedProductTierPriceManagementInterface" type="\Magento\Catalog\Model\Product\ScopedTierPriceManagement" /> <preference for="Magento\Catalog\Api\Data\ProductTierPriceInterface" type="Magento\Catalog\Model\Product\TierPrice" /> <preference for="Magento\Catalog\Api\Data\CategoryProductLinkInterface" type="Magento\Catalog\Model\CategoryProductLink" /> <preference for="Magento\Catalog\Api\ProductCustomOptionTypeListInterface" type="Magento\Catalog\Model\ProductOptions\TypeList" /> diff --git a/app/code/Magento/Catalog/etc/extension_attributes.xml b/app/code/Magento/Catalog/etc/extension_attributes.xml index 976031cb937fe414bb97eff0560e1496ab3ac691..509c3240bb6c8046ae74fcc36a55160376e195f1 100644 --- a/app/code/Magento/Catalog/etc/extension_attributes.xml +++ b/app/code/Magento/Catalog/etc/extension_attributes.xml @@ -25,4 +25,10 @@ <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="category_links" type="Magento\Catalog\Api\Data\CategoryLinkInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\ProductTierPriceInterface"> + <attribute code="percentage_value" type="float" /> + </extension_attributes> + <extension_attributes for="Magento\Catalog\Api\Data\ProductTierPriceInterface"> + <attribute code="website_id" type="int" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index 87e82543fc65b2991d5f10da91941f389dc18fcd..1250b55b968480bf9c1d9f9952da6d864048d98f 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Catalog" setup_version="2.0.7"> + <module name="Magento_Catalog" setup_version="2.1.0"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Cms"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js new file mode 100644 index 0000000000000000000000000000000000000000..0939619809a460b49cc7f254702797357c3b06f5 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js @@ -0,0 +1,15 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/form/element/abstract' +], function (Abstract) { + 'use strict'; + + return Abstract.extend({ + defaults: { + elementTmpl: 'Magento_Catalog/form/element/price-input' + } + }); +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html new file mode 100644 index 0000000000000000000000000000000000000000..ce8ae751e6702ff11099db5a06c8dbb8d4b1cbb9 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html @@ -0,0 +1,22 @@ +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<input class="admin__control-text" type="text" + data-bind=" + event: { + change: userChanges, + input: onInput + }, + value: value, + hasFocus: focused, + valueUpdate: valueUpdate, + attr: { + name: inputName, + placeholder: placeholder, + 'aria-describedby': noticeId, + id: uid, + disabled: disabled + }"/> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index 362aac7c8cf309523fedae6ea833e08f1bab789b..64e0d2ec59d105f2e312a1e24a5b5ab864b8eec7 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -242,7 +242,7 @@ </argument> <field name="customer_group_id"> <argument name="data" xsi:type="array"> - <item name="options" xsi:type="object">Magento\Customer\Model\Customer\Source\Group</item> + <item name="options" xsi:type="object">Magento\Customer\Model\Customer\Source\GroupSourceInterface</item> <item name="config" xsi:type="array"> <item name="dataType" xsi:type="string">text</item> <item name="formElement" xsi:type="string">select</item> diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml index 3aff684574652c2c9de84a2a6713fa8d7002d82a..af60e0247169bb402d5b6d71be4576ad2ed24566 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml +++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml @@ -149,7 +149,7 @@ <item name="source" xsi:type="string">catalog_rule</item> <item name="dataScope" xsi:type="string">customer_group_ids</item> </item> - <item name="options" xsi:type="object">Magento\CatalogRule\Model\Rule\CustomerGroupsOptionsProvider</item> + <item name="options" xsi:type="object">\Magento\Customer\Model\Customer\Source\GroupSourceInterface</item> </argument> </field> <field name="from_date"> diff --git a/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php b/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php new file mode 100644 index 0000000000000000000000000000000000000000..4c321f521bb79491ab282545b13d29040cc382bd --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Setup; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; + +/** + * Upgrade Data script + * @codeCoverageIgnore + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * EAV setup factory + * + * @var EavSetupFactory + */ + private $eavSetupFactory; + + /** + * Init + * + * @param EavSetupFactory $eavSetupFactory + */ + public function __construct(EavSetupFactory $eavSetupFactory) + { + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * {@inheritdoc} + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.2.0') < 0) { + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); + $relatedProductTypes = explode( + ',', + $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'tier_price', 'apply_to') + ); + $key = array_search(Configurable::TYPE_CODE, $relatedProductTypes); + if ($key !== false) { + unset($relatedProductTypes[$key]); + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'tier_price', + 'apply_to', + implode(',', $relatedProductTypes) + ); + } + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index 21730cf65f020f5766fafe123b4448a1f387f6f9..efb409dd156af2af33f6a233a583fdf8a3fcedd0 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -384,7 +384,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase 'addAttributeToSelect', 'addFilterByRequiredOptions', 'setStoreId', - 'addPriceData', + 'addTierPriceData', 'getIterator', 'load', ] @@ -393,7 +393,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase $productCollection->expects($this->any())->method('addAttributeToSelect')->will($this->returnSelf()); $productCollection->expects($this->any())->method('setProductFilter')->will($this->returnSelf()); $productCollection->expects($this->any())->method('setFlag')->will($this->returnSelf()); - $productCollection->expects($this->any())->method('addPriceData')->will($this->returnSelf()); + $productCollection->expects($this->any())->method('addTierPriceData')->will($this->returnSelf()); $productCollection->expects($this->any())->method('addFilterByRequiredOptions')->will($this->returnSelf()); $productCollection->expects($this->any())->method('setStoreId')->with(5)->will($this->returnValue([])); $productCollection->expects($this->any())->method('getIterator')->willReturn( diff --git a/app/code/Magento/ConfigurableProduct/etc/module.xml b/app/code/Magento/ConfigurableProduct/etc/module.xml index 8a64e9f026d9d86fd22b5c4d8fb8bf40cb781c29..706338092c4b6ae56d25dd3c125a728578b6be19 100644 --- a/app/code/Magento/ConfigurableProduct/etc/module.xml +++ b/app/code/Magento/ConfigurableProduct/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_ConfigurableProduct" setup_version="2.0.0"> + <module name="Magento_ConfigurableProduct" setup_version="2.2.0"> <sequence> <module name="Magento_Catalog"/> <module name="Magento_Msrp"/> diff --git a/app/code/Magento/Customer/Model/Config/Source/Group.php b/app/code/Magento/Customer/Model/Config/Source/Group.php index 40c7712f5aab3724071ef9def4ecd27eb5e1b8a5..e693bf7a3c45e29791dede512a6b00a293633cf7 100644 --- a/app/code/Magento/Customer/Model/Config/Source/Group.php +++ b/app/code/Magento/Customer/Model/Config/Source/Group.php @@ -6,6 +6,8 @@ namespace Magento\Customer\Model\Config\Source; use Magento\Customer\Api\GroupManagementInterface; +use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface; +use Magento\Framework\App\ObjectManager; class Group implements \Magento\Framework\Option\ArrayInterface { @@ -15,37 +17,48 @@ class Group implements \Magento\Framework\Option\ArrayInterface protected $_options; /** + * @deprecated * @var GroupManagementInterface */ protected $_groupManagement; /** + * @deprecated * @var \Magento\Framework\Convert\DataObject */ protected $_converter; + /** + * @var GroupSourceLoggedInOnlyInterface + */ + private $groupSourceLoggedInOnly; + /** * @param GroupManagementInterface $groupManagement * @param \Magento\Framework\Convert\DataObject $converter + * @param GroupSourceLoggedInOnlyInterface $groupSourceForLoggedInCustomers */ public function __construct( GroupManagementInterface $groupManagement, - \Magento\Framework\Convert\DataObject $converter + \Magento\Framework\Convert\DataObject $converter, + GroupSourceLoggedInOnlyInterface $groupSourceForLoggedInCustomers = null ) { $this->_groupManagement = $groupManagement; $this->_converter = $converter; + $this->groupSourceLoggedInOnly = $groupSourceForLoggedInCustomers + ?: ObjectManager::getInstance()->get(GroupSourceLoggedInOnlyInterface::class); } /** - * @return array + * @inheritdoc */ public function toOptionArray() { if (!$this->_options) { - $groups = $this->_groupManagement->getLoggedInGroups(); - $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code'); + $this->_options = $this->groupSourceLoggedInOnly->toOptionArray(); array_unshift($this->_options, ['value' => '', 'label' => __('-- Please Select --')]); } + return $this->_options; } } diff --git a/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php b/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php index 708e3243e26992589f553ddf4e7eda6bc19ed02b..adfc78448cdfc1b29412a9e5e5caca3907c6e487 100644 --- a/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php +++ b/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php @@ -5,7 +5,9 @@ */ namespace Magento\Customer\Model\Config\Source\Group; +use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface; use Magento\Customer\Api\GroupManagementInterface; +use Magento\Framework\App\ObjectManager; class Multiselect implements \Magento\Framework\Option\ArrayInterface { @@ -17,25 +19,36 @@ class Multiselect implements \Magento\Framework\Option\ArrayInterface protected $_options; /** + * @deprecated * @var GroupManagementInterface */ protected $_groupManagement; /** + * @deprecated * @var \Magento\Framework\Convert\DataObject */ protected $_converter; + /** + * @var GroupSourceLoggedInOnlyInterface + */ + private $groupSourceLoggedInOnly; + /** * @param GroupManagementInterface $groupManagement * @param \Magento\Framework\Convert\DataObject $converter + * @param GroupSourceLoggedInOnlyInterface|null $groupSourceLoggedInOnly */ public function __construct( GroupManagementInterface $groupManagement, - \Magento\Framework\Convert\DataObject $converter + \Magento\Framework\Convert\DataObject $converter, + GroupSourceLoggedInOnlyInterface $groupSourceLoggedInOnly = null ) { $this->_groupManagement = $groupManagement; $this->_converter = $converter; + $this->groupSourceLoggedInOnly = $groupSourceLoggedInOnly + ?: ObjectManager::getInstance()->get(GroupSourceLoggedInOnlyInterface::class); } /** @@ -46,8 +59,7 @@ class Multiselect implements \Magento\Framework\Option\ArrayInterface public function toOptionArray() { if (!$this->_options) { - $groups = $this->_groupManagement->getLoggedInGroups(); - $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code'); + $this->_options = $this->groupSourceLoggedInOnly->toOptionArray(); } return $this->_options; } diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php index 4663d26b628b09d13a9e2a1556f03a31b951a181..9af4b7fc67340b390aec50dc4fb0c2891aef03e3 100644 --- a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php +++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php @@ -12,7 +12,7 @@ use Magento\Customer\Api\GroupManagementInterface; * * @author Magento Core Team <core@magentocommerce.com> */ -class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table +class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table implements GroupSourceLoggedInOnlyInterface { /** * @var GroupManagementInterface @@ -50,6 +50,7 @@ class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table $groups = $this->_groupManagement->getLoggedInGroups(); $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code'); } + return $this->_options; } } diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5570b4073dfdba163fb403c528790e26d904713c --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Model\Customer\Attribute\Source; + +use \Magento\Framework\Data\OptionSourceInterface; + +interface GroupSourceLoggedInOnlyInterface extends OptionSourceInterface +{ + +} diff --git a/app/code/Magento/Customer/Model/Customer/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Source/Group.php index 59dc3be52f1e57f87d7cd6b7888f27050f83e1dc..5973ec6ffaab3e91a706e4ec542500ec61b41b29 100644 --- a/app/code/Magento/Customer/Model/Customer/Source/Group.php +++ b/app/code/Magento/Customer/Model/Customer/Source/Group.php @@ -5,12 +5,13 @@ */ namespace Magento\Customer\Model\Customer\Source; +use Magento\Customer\Api\Data\GroupSearchResultsInterface; use Magento\Framework\Module\Manager as ModuleManager; use Magento\Customer\Api\Data\GroupInterface; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; -class Group implements \Magento\Framework\Option\ArrayInterface +class Group implements GroupSourceInterface { /** * @var ModuleManager @@ -52,14 +53,13 @@ class Group implements \Magento\Framework\Option\ArrayInterface if (!$this->moduleManager->isEnabled('Magento_Customer')) { return []; } - $customerGroups = [ - [ - 'label' => __('ALL GROUPS'), - 'value' => GroupInterface::CUST_GROUP_ALL, - ] + $customerGroups = []; + $customerGroups[] = [ + 'label' => __('ALL GROUPS'), + 'value' => GroupInterface::CUST_GROUP_ALL, ]; - /** @var GroupInterface[] $groups */ + /** @var GroupSearchResultsInterface $groups */ $groups = $this->groupRepository->getList($this->searchCriteriaBuilder->create()); foreach ($groups->getItems() as $group) { $customerGroups[] = [ diff --git a/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php b/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c044740dc35e75ec46f5b20d66b79ad5832a31f9 --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Customer\Source; + +use Magento\Framework\Data\OptionSourceInterface; + +interface GroupSourceInterface extends OptionSourceInterface +{ + +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php index 3b8bea594ed523e268e13f5297cd00db2fdae0c0..bc64351f3d01591cea94892b759e08cefeb7d81d 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php @@ -6,6 +6,8 @@ */ namespace Magento\Customer\Test\Unit\Model\Config\Source\Group; +use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface; + class MultiselectTest extends \PHPUnit_Framework_TestCase { /** @@ -23,22 +25,29 @@ class MultiselectTest extends \PHPUnit_Framework_TestCase */ protected $converterMock; + /** + * @var GroupSourceLoggedInOnlyInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $groupSourceLoggedInOnly; + protected function setUp() { $this->groupServiceMock = $this->getMock(\Magento\Customer\Api\GroupManagementInterface::class); $this->converterMock = $this->getMock(\Magento\Framework\Convert\DataObject::class, [], [], '', false); - $this->model = - new \Magento\Customer\Model\Config\Source\Group\Multiselect($this->groupServiceMock, $this->converterMock); + $this->groupSourceLoggedInOnly = $this->getMockBuilder(GroupSourceLoggedInOnlyInterface::class)->getMock(); + $this->model = new \Magento\Customer\Model\Config\Source\Group\Multiselect( + $this->groupServiceMock, + $this->converterMock, + $this->groupSourceLoggedInOnly + ); } public function testToOptionArray() { $expectedValue = ['General', 'Retail']; - $this->groupServiceMock->expects($this->once()) - ->method('getLoggedInGroups') - ->will($this->returnValue($expectedValue)); - $this->converterMock->expects($this->once())->method('toOptionArray') - ->with($expectedValue, 'id', 'code')->will($this->returnValue($expectedValue)); + $this->groupServiceMock->expects($this->never())->method('getLoggedInGroups'); + $this->converterMock->expects($this->never())->method('toOptionArray'); + $this->groupSourceLoggedInOnly->expects($this->once())->method('toOptionArray')->willReturn($expectedValue); $this->assertEquals($expectedValue, $this->model->toOptionArray()); } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php index edb3405451ecab918ca046f91db5a7ce1dcc037d..55b495f9c419b4d2a208f2209f9c7049564fa367 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php @@ -6,10 +6,21 @@ */ namespace Magento\Customer\Test\Unit\Model\Config\Source; +use Magento\Customer\Api\GroupManagementInterface; +use Magento\Customer\Model\Config\Source\Group; +use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface; +use Magento\Framework\Convert\DataObject; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + class GroupTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Customer\Model\Config\Source\Group + * @var GroupSourceLoggedInOnlyInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $groupSource; + + /** + * @var Group */ protected $model; @@ -25,20 +36,30 @@ class GroupTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->groupServiceMock = $this->getMock(\Magento\Customer\Api\GroupManagementInterface::class); - $this->converterMock = $this->getMock(\Magento\Framework\Convert\DataObject::class, [], [], '', false); - $this->model = - new \Magento\Customer\Model\Config\Source\Group($this->groupServiceMock, $this->converterMock); + $this->groupServiceMock = $this->getMock(GroupManagementInterface::class); + $this->converterMock = $this->getMock(DataObject::class, [], [], '', false); + $this->groupSource = $this->getMockBuilder(GroupSourceLoggedInOnlyInterface::class) + ->getMockForAbstractClass(); + $this->model = (new ObjectManager($this))->getObject( + Group::class, + [ + 'groupManagement' => $this->groupServiceMock, + 'converter' => $this->converterMock, + 'groupSourceForLoggedInCustomers' => $this->groupSource, + ] + ); } public function testToOptionArray() { $expectedValue = ['General', 'Retail']; - $this->groupServiceMock->expects($this->once()) - ->method('getLoggedInGroups') - ->will($this->returnValue($expectedValue)); - $this->converterMock->expects($this->once())->method('toOptionArray') - ->with($expectedValue, 'id', 'code')->will($this->returnValue($expectedValue)); + $this->groupServiceMock->expects($this->never())->method('getLoggedInGroups'); + $this->converterMock->expects($this->never())->method('toOptionArray'); + + $this->groupSource->expects($this->once()) + ->method('toOptionArray') + ->willReturn($expectedValue); + array_unshift($expectedValue, ['value' => '', 'label' => __('-- Please Select --')]); $this->assertEquals($expectedValue, $this->model->toOptionArray()); } diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index d8f320161ab060b9cce690848fb00a402f030bb4..ebde958f75bc1f678aafd7ccd677c1536f76ef69 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -51,6 +51,10 @@ type="Magento\Customer\Helper\View" /> <preference for="Magento\Customer\Model\Address\CustomAttributeListInterface" type="Magento\Customer\Model\Address\CustomAttributeList" /> + <preference for="Magento\Customer\Model\Customer\Source\GroupSourceInterface" + type="Magento\Customer\Model\Customer\Source\Group" /> + <preference for="Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface" + type="Magento\Customer\Model\Customer\Attribute\Source\Group"/> <type name="Magento\Customer\Model\Session"> <arguments> <argument name="configShare" xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument> diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php index 6287af52bb8896cbdb0dbf38a4756493a26cde8f..45d120db4755bb7c461cd575473810baf0077198 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php @@ -194,12 +194,17 @@ abstract class AbstractForm extends \Magento\Sales\Block\Adminhtml\Order\Create\ if ($inputType == 'select' || $inputType == 'multiselect') { $options = []; foreach ($attribute->getOptions() as $optionData) { - $options[] = ConvertArray::toFlatArray( - $this->dataObjectProcessor->buildOutputDataArray( - $optionData, - \Magento\Customer\Api\Data\OptionInterface::class - ) + $data = $this->dataObjectProcessor->buildOutputDataArray( + $optionData, + \Magento\Customer\Api\Data\OptionInterface::class ); + foreach ($data as $key => $value) { + if (is_array($value)) { + unset($data[$key]); + $data['value'] = $value; + } + } + $options[] = $data; } $element->setValues($options); } elseif ($inputType == 'date') { diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js index f68e322a49dea5256093efded3fd9feaf827461d..11643d075a7965641617abc33440116bb4fd6fa1 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js @@ -38,7 +38,10 @@ define([ el.innerHTML = elem; actions = el.getElementsByClassName(actionsClass)[0]; - el.removeChild(actions); + + if (actions) { + el.removeChild(actions); + } return el.innerHTML; } diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index 764b11dfbfea202bf40c8e631fdbcfdc0da1285e..7823871fd694c362912e0479a408d7befcf25450 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -27,12 +27,16 @@ define([ message: '' }; + if (_.isObject(params)) { + message = params.message || ''; + } + if (!rulesList[id]) { return result; } rule = rulesList[id]; - message = rule.message; + message = message || rule.message; valid = rule.handler(value, params, additionalParams); if (!valid) { @@ -68,7 +72,7 @@ define([ }; _.every(rules, function (ruleParams, id) { - if (ruleParams !== false || additionalParams) { + if (ruleParams.validate || ruleParams !== false || additionalParams) { result = validate(id, value, ruleParams, additionalParams); return result.passed; diff --git a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less index 585269e14d685dd8bc3e2f4ebcd90e1b500881da..bdb94e3a8202f9dc0142d5e81c5d6941e1567835 100644 --- a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less @@ -8,7 +8,16 @@ // _____________________________________________ @message-system__background-color: @color-lazy-sun; -@message-system-short-message__padding-vertical: 1.8rem; +@message-system__border-color: @color-gray82; +@message-system__border-width: .1rem; +@message-system__color: @color-gray20; +@message-system-short__padding-vertical: 1.5rem; +@message-system-short-wrapper__height: 5rem; + +// Triangle marker +@message-system-triangle__height: .5rem; +@message-system-triangle__padding-right: 3rem; +@message-system-triangle__width: .8rem; // // Message system @@ -17,34 +26,124 @@ .message-system-inner { &:extend(.abs-clearfix all); background: @message-system__background-color; + border: solid @message-system__border-color; + border-width: 0 @message-system__border-width @message-system__border-width; + position: relative; + + .message-error { + background: none; + } + + .message { + background: none; + margin: 0 0 -3px; + overflow: hidden; + padding: @message-system-short__padding-vertical 0 @message-system-short__padding-vertical 3.3rem; + + &:before { + left: .3rem; + } + } - .message-system-list { - float: left; - width: 75%; + .action-menu-item { + &.action-close-wrapper { + width: 3.5rem; + } + + .action-close { + float: right; + } + + float: right; + padding: @message-system-short__padding-vertical 0 0; + vertical-align: top; } } .message-system-list { + border-bottom: 1px solid @message-system__border-color; + border-top: 1px solid @message-system__border-color; list-style: none; - margin: 0; - padding: 0; + margin: 0 0 1.5rem; + + li { + + li { + border-top: 1px dashed @message-system__border-color; + } + } } .message-system-short { + min-height: @message-system-short-wrapper__height; + + .action-close-wrapper { + display: none; + } +} + +.message-system-short-wrapper { overflow: hidden; - text-align: right; + padding: 0 1.5rem 0 @indent__l; +} - .message-system-short-label { - display: inline-block; - padding: @message-system-short-message__padding-vertical .3rem @message-system-short-message__padding-vertical 1rem; +.message-system-collapsible { + background: @message-system__background-color; + border: @message-system__border-width solid @message-system__border-color; + border-top: 0; + display: none; + left: -1px; + padding: 0 @indent__l @message-system-short__padding-vertical; + position: absolute; + right: -1px; + top: 100%; + z-index: @z-index-5 - 2; + + ._active & { + display: block; } +} - .message { +.message-system-action-dropdown { + .lib-button-reset(); + float: right; + margin: @message-system-short__padding-vertical 0; + position: relative; + + .action-toggle-triangle ( + @_dropdown__padding-right: @message-system-triangle__padding-right; + @_triangle__height: @message-system-triangle__height; + @_triangle__width: @message-system-triangle__width; + @_triangle__color: @message-system__color; + @_triangle__hover__color: darken(@message-system__color, 10%); + @_triangle__right: (@message-system-triangle__padding-right / 2) - (@message-system-triangle__width/ 2); + ); +} + +.message-system-summary { + text-align: right; + + .action__message-log { + border-right: 1px solid @message-system__border-color; display: inline-block; - padding: @message-system-short-message__padding-vertical 2rem @message-system-short-message__padding-vertical 3.3rem; + margin: 0 .2rem 0 @indent__xs; + padding-right: @indent__xs; - &:before { - left: .3rem; + &:last-child { + border-right: 0; + margin: 0; + padding: 0; } } } + +.notices-wrapper { + .admin__data-grid-loading-mask { + display: none; + min-height: @message-system-short-wrapper__height + @message-system__border-width; + z-index: @z-index-5 - 1; + } + + .admin__data-grid-outer-wrap { + min-height: 0; + } +} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less index 9d9a4cab990477f594601edefce714b2e1ad90ef..e955affe3fb4394a9daa38eeba643b64d8c1e658 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less @@ -28,6 +28,9 @@ @alert-icon__warning__content: @icon-warning__content; @alert-icon__warning__color: @color-phoenix; +@alert-icon__progress__content: @icon-refresh__content; +@alert-icon__progress__color: @color-blue-dodger; + @alert-icon__success__content: @icon-check-mage__content; @alert-icon__success__color: @color-green-apple; @@ -93,6 +96,13 @@ } } +.message-progress { + &:before { + color: @alert-icon__progress__color; + content: @alert-icon__progress__content; + } +} + .message-error { background: @alert__error__background-color; @@ -128,4 +138,4 @@ .message-in-rating-edit { margin-left: 1.8rem; margin-right: 1.8rem; -} +} \ No newline at end of file diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index b1a1e7a36858db51016f7282e5bad9ddde2e9259..ff7968292874eaf63f7c3970c11b3216868606a1 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -51,7 +51,7 @@ class ProductTierPriceManagementTest extends WebapiAbstract public function getListDataProvider() { return [ - [0, 1, 5, 3], + [0, 2, 5, 3], [1, 0, null, null], ['all', 2, 8, 2], ]; diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml index 20359c41bd55fb148c4dd407a0b75c4e2cda8f93..bd2a0e60b20ab032b034a2930f1bd1e24f54f7fc 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml @@ -7,6 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="BraintreeSettlementReportIndex" area="Adminhtml" mca="braintree/report/index" module="Magento_Braintree"> - <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/> + <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator="//div[contains(@data-bind, 'braintree_report')]" strategy="xpath"/> </page> </config> 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 f3d839ecf8a8e5e9f71d5617c2e62f2b90559dd8..4ec372ab7163bb627bdf1353618f81231bed504d 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 @@ -347,8 +347,16 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface $priceInfo['customer_group_id'] = $priceInfo['cust_group']; unset($priceInfo['cust_group']); - $priceInfo['value'] = $priceInfo['price']; - unset($priceInfo['price']); + if (isset($priceInfo['price'])) { + $priceInfo['value'] = $priceInfo['price']; + unset($priceInfo['price']); + } + unset($priceInfo['value_type']); + + if (isset($priceInfo['percentage_value'])) { + $priceInfo['extension_attributes']['percentage_value'] = $priceInfo['percentage_value']; + unset($priceInfo['percentage_value']); + } $priceInfo['qty'] = $priceInfo['price_qty']; unset($priceInfo['price_qty']); diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml index a1de0d5599564a5693604efea577ae2a14e0d76e..c6a125bfd4ed236e4c8a6db38f6bfd7fb11a3e85 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="CatalogProductIndex" area="Adminhtml" mca="catalog/product/index" module="Magento_Catalog"> - <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/> + <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator="//div[contains(@data-bind, 'product_listing')]" strategy="xpath"/> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/> <block name="gridPageActionBlock" class="Magento\Catalog\Test\Block\Adminhtml\Product\GridPageAction" locator="#add_new_product" strategy="css selector"/> </page> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml index e23472508e83a83ca56d6e738bed43f3ca65bd88..c369a950df315087b09ff9528aa9c9227d258d18 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml @@ -9,6 +9,6 @@ <page name="CmsBlockIndex" area="Adminhtml" mca="cms/block/index" module="Magento_Cms"> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" /> <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" /> - <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" /> + <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator="//div[contains(@data-bind, 'cms_block_listing')]" strategy="xpath" /> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml index ed2037cf893a39a14d6eee047f14c69da04f3dea..467d8f8a2ffe8309ef7964d4cdf925ab3544a347 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="CmsPageIndex" area="Adminhtml" mca="cms/page/index" module="Magento_Cms"> <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" /> - <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector" /> + <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator="//div[contains(@data-bind, 'cms_page_listing')]" strategy="xpath" /> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" /> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml index d0247118d2d3b4261da1ba0e288f270f7ebd8ab3..7b908210b6e7f58de28dcf8188da38748b9a6e05 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml @@ -9,6 +9,6 @@ <page name="CustomerIndex" area="Adminhtml" mca="customer/index" module="Magento_Customer"> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/> <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/> - <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/> + <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator="//div[contains(@data-bind, 'customer_listing')]" strategy="xpath"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php index bd7d10a7cf04e5f4a1995edad8cbaec1d7d28fa1..3c2a9ed9c9b0fd2b09ea9435ce8cb0b43b46a58e 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php @@ -96,7 +96,7 @@ class Grid extends DataGrid $this->openFilterBlock(); $storeGroupElements = $this->_rootElement->find($this->filters['purchase_point']['selector']) - ->getElements('//option/preceding-sibling::optgroup[1]', Locator::SELECTOR_XPATH); + ->getElements('.//option/preceding-sibling::optgroup[1]', Locator::SELECTOR_XPATH); $result = []; foreach ($storeGroupElements as $storeGroupElement) { diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml index 1382f1c2567ddc1aac85673369703e632676bd11..beee22011c69e80d0ab7d20516a2193518ae0371 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="OrderIndex" area="Adminhtml" mca="sales/order/index" module="Magento_Sales"> <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/> - <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/> + <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator="//div[contains(@data-bind, 'sales_order_grid')]" strategy="xpath"/> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml index 85cc69ea56584e326ffabd656e694b11a1127681..a979454142f42adf17f8a566de68efd80b083bd6 100644 --- a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml @@ -9,6 +9,6 @@ <page name="synonymGroupIndex" area="Adminhtml" mca="search/synonyms/index" module="Magento_Search"> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" /> <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" /> - <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" /> + <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator="//div[contains(@data-bind, 'search_synonyms_grid')]" strategy="xpath" /> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml index 81667dbbcc54dd5bf66c0c600a615d5709a01759..e9d5e1d21175ddb56d016c12af6c89e6c9fd20d0 100644 --- a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="ShipmentIndex" area="Adminhtml" mca="sales/shipment" module="Magento_Shipping"> - <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/> + <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator="//div[contains(@data-bind, 'sales_order_shipment_grid')]" strategy="xpath"/> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/> </page> </config> diff --git a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php index e32939b195b7dfde6a9dd3a09c55dc14af2efed2..e5fd4cc0d9630080eb7c06f2558b02be889f8a57 100644 --- a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php +++ b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php @@ -124,7 +124,11 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase $this->assertEquals(3, count($tierPriceCollection)); /** @var \Magento\Catalog\Model\Product\TierPrice $tierPrice */ foreach ($tierPriceCollection as $tierPrice) { - $this->assertContains($tierPrice->getData(), $this->expectedTierPrice[$sku]); + $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getPercentageValue()); + $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getWebsiteId()); + $tierPriceData = $tierPrice->getData(); + unset($tierPriceData['extension_attributes']); + $this->assertContains($tierPriceData, $this->expectedTierPrice[$sku]); } } } @@ -240,7 +244,11 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase $this->assertEquals(3, count($tierPriceCollection)); /** @var \Magento\Catalog\Model\Product\TierPrice $tierPrice */ foreach ($tierPriceCollection as $tierPrice) { - $this->assertContains($tierPrice->getData(), $this->expectedTierPrice[$sku]); + $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getPercentageValue()); + $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getWebsiteId()); + $tierPriceData = $tierPrice->getData(); + unset($tierPriceData['extension_attributes']); + $this->assertContains($tierPriceData, $this->expectedTierPrice[$sku]); } } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php index c69bdc1738bd471b14ac31a3ddeb432b632ed441..89dd932f8a023c4adb2c7cc17cc04ad2eddfd6c6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php @@ -95,6 +95,21 @@ class TierpriceTest extends \PHPUnit_Framework_TestCase $this->_model->validate($product); } + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testValidatePercentage() + { + $product = new \Magento\Framework\DataObject(); + $product->setTierPrice( + [ + ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'percentage_value' => 101], + ] + ); + + $this->_model->validate($product); + } + public function testPreparePriceData() { $data = [ @@ -122,7 +137,7 @@ class TierpriceTest extends \PHPUnit_Framework_TestCase $this->_model->afterLoad($product); $price = $product->getTierPrice(); $this->assertNotEmpty($price); - $this->assertEquals(3, count($price)); + $this->assertEquals(4, count($price)); } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index f0a665d8be8b7eea90480af5fad416b2659337d3..7f13f6c9c52962d5876f62f2182205e825e6aa20 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -17,6 +17,11 @@ class CollectionTest extends \PHPUnit_Framework_TestCase */ protected $processor; + /** + * @var \Magento\Catalog\Api\ProductRepositoryInterface + */ + private $productRepository; + /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. @@ -30,6 +35,10 @@ class CollectionTest extends \PHPUnit_Framework_TestCase $this->processor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\Indexer\Product\Price\Processor::class ); + + $this->productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Api\ProductRepositoryInterface::class + ); } /** @@ -100,4 +109,19 @@ class CollectionTest extends \PHPUnit_Framework_TestCase $this->assertCount(2, $items); $this->assertEquals(15, $product->getPrice()); } + + /** + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php + * @magentoDbIsolation enabled + */ + public function testGetProductsWithTierPrice() + { + $product = $this->productRepository->get('simple products'); + $items = $this->collection->addIdFilter($product->getId())->addAttributeToSelect('price') + ->load()->addTierPriceData(); + $tierPrices = $items->getFirstItem()->getTierPrices(); + $this->assertCount(3, $tierPrices); + $this->assertEquals(50, $tierPrices[2]->getExtensionAttributes()->getPercentageValue()); + $this->assertEquals(5, $tierPrices[2]->getValue()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php index 60e5ad4f8b76a5789de5ca476a3f7cb453f35fa8..c1a7ef37dacc5c3bfc169af3b4f868371433c055 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php @@ -10,7 +10,6 @@ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\Catalog\Model\Product::class); $product->isObjectNew(true); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId(2) ->setAttributeSetId(4) ->setWebsiteIds([1]) ->setName('Simple Products') @@ -33,6 +32,12 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) 'price_qty' => 21, 'price' => 81, ], + [ + 'website_id' => 0, + 'cust_group' => Group::CUST_GROUP_ALL, + 'price_qty' => 30, + 'percentage_value' => 50, + ], ] ) ->setDescription('Description with <b>html tag</b>') diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php index 794a2f9087a8ec7035f92e042008c35624b62653..aadf1e74a883ce823d574178cba29ca617f5abf0 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; + \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); /** @var \Magento\TestFramework\ObjectManager $objectManager */ @@ -12,6 +14,92 @@ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ $categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); +$tierPrices = []; +/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 2, + 'value' => 8 + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 5, + 'value' => 5 + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 3, + 'value' => 5 + ] + ] +); +/** @var $tpExtensionAttributes */ +$tpExtensionAttributesFactory = $objectManager->create(ProductTierPriceExtensionFactory::class); +$tpExtensionAttributes = $tpExtensionAttributesFactory->create()->setPercentageValue(50); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 10 + ] + ] +)->setExtensionAttributes($tpExtensionAttributes); + +$tierPrices = []; +/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 2, + 'value' => 8 + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, + 'qty' => 5, + 'value' => 5 + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 3, + 'value' => 5 + ] + ] +); +/** @var $tpExtensionAttributes */ +$tpExtensionAttributesFactory = $objectManager->create(ProductTierPriceExtensionFactory::class); +$tpExtensionAttributes = $tpExtensionAttributesFactory->create()->setPercentageValue(50); + +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, + 'qty' => 10 + ] + ] +)->setExtensionAttributes($tpExtensionAttributes); + /** @var $product \Magento\Catalog\Model\Product */ $product = $objectManager->create(\Magento\Catalog\Model\Product::class); $product->isObjectNew(true); @@ -25,28 +113,7 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) ->setWeight(1) ->setShortDescription("Short description") ->setTaxClassId(0) - ->setTierPrice( - [ - [ - 'website_id' => 0, - 'cust_group' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, - 'price_qty' => 2, - 'price' => 8, - ], - [ - 'website_id' => 0, - 'cust_group' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, - 'price_qty' => 5, - 'price' => 5, - ], - [ - 'website_id' => 0, - 'cust_group' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, - 'price_qty' => 3, - 'price' => 5, - ], - ] - ) + ->setTierPrices($tierPrices) ->setDescription('Description with <b>html tag</b>') ->setMetaTitle('meta title') ->setMetaKeyword('meta keyword') diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php index d337d9a764265d8d98f7e2d20a503d98fd0342fe..899572a500e6ba281912575973757065c823738f 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php @@ -18,13 +18,24 @@ class MultiselectTest extends \PHPUnit_Framework_TestCase $multiselect = Bootstrap::getObjectManager()->get( \Magento\Customer\Model\Config\Source\Group\Multiselect::class ); + + $options = $multiselect->toOptionArray(); + $optionsToCompare = []; + foreach ($options as $option) { + if (is_array($option['value'])) { + $optionsToCompare = array_merge($optionsToCompare, $option['value']); + } else { + $optionsToCompare[] = $option; + } + } + sort($optionsToCompare); $this->assertEquals( [ ['value' => 1, 'label' => 'General'], ['value' => 2, 'label' => 'Wholesale'], ['value' => 3, 'label' => 'Retailer'], ], - $multiselect->toOptionArray() + $optionsToCompare ); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php index b4674c7b6aae33b309777b1db4c78662a098a7ce..3d0776d058f44f1f22a46bf224c4233e7c91f4d7 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php @@ -16,14 +16,33 @@ class GroupTest extends \PHPUnit_Framework_TestCase { /** @var Group $group */ $group = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Config\Source\Group::class); - $this->assertEquals( - [ - ['value' => '', 'label' => '-- Please Select --'], - ['value' => 1, 'label' => 'General'], - ['value' => 2, 'label' => 'Wholesale'], - ['value' => 3, 'label' => 'Retailer'], - ], - $group->toOptionArray() + $options = $group->toOptionArray(); + $this->assertContainsOptionRecursive('', '-- Please Select --', $options); + } + + private function assertContainsOptionRecursive($expectedValue, $expectedLabel, array $values) + { + $this->assertTrue( + $this->hasOptionLabelRecursive($expectedValue, $expectedLabel, $values), + 'Label ' . $expectedLabel . ' not found' ); } + + private function hasOptionLabelRecursive($value, $label, array $values) + { + $hasLabel = false; + foreach ($values as $option) { + $this->assertArrayHasKey('label', $option); + $this->assertArrayHasKey('value', $option); + if (strpos((string)$option['label'], (string)$label) !== false) { + $this->assertEquals($value, $option['value']); + $hasLabel = true; + break; + } elseif (is_array($option['value'])) { + $hasLabel |= $this->hasOptionLabelRecursive($value, $label, $option['value']); + } + } + + return (bool)$hasLabel; + } } diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php index c1e9be87a9ab6a1f5e4bb6ce6896dc23594b601e..e3810fc0766bd7a1028cbc209a092a655b5f5ca0 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php @@ -52,11 +52,17 @@ class EavAbstractTest extends \PHPUnit_Framework_TestCase $index = $attribute->getAttributeCode() == $indexAttributeCode ? 'value' : 'label'; $expectedOptions = []; foreach ($attribute->getSource()->getAllOptions(false) as $option) { - $expectedOptions[strtolower($option[$index])] = $option['value']; + if (is_array($option['value'])) { + foreach ($option['value'] as $value) { + $expectedOptions[strtolower($value[$index])] = $value['value']; + } + } else { + $expectedOptions[strtolower($option[$index])] = $option['value']; + } } $actualOptions = $this->_model->getAttributeOptions($attribute, [$indexAttributeCode]); - sort($expectedOptions); - sort($actualOptions); + asort($expectedOptions); + asort($actualOptions); $this->assertEquals($expectedOptions, $actualOptions); } } diff --git a/lib/internal/Magento/Framework/Module/Dir/Reader.php b/lib/internal/Magento/Framework/Module/Dir/Reader.php index 7a8b1f11ef184654ee6e8318438610f552907809..353f7e51c590687823303f68aef2cd7899d2452f 100644 --- a/lib/internal/Magento/Framework/Module/Dir/Reader.php +++ b/lib/internal/Magento/Framework/Module/Dir/Reader.php @@ -46,6 +46,13 @@ class Reader */ protected $readFactory; + /** + * Found configuration files grouped by configuration types (filename). + * + * @var array + */ + private $fileIterators = []; + /** * @param Dir $moduleDirs * @param ModuleListInterface $moduleList @@ -65,24 +72,42 @@ class Reader } /** - * Go through all modules and find configuration files of active modules + * Go through all modules and find configuration files of active modules. * * @param string $filename * @return FileIterator */ public function getConfigurationFiles($filename) { - return $this->fileIteratorFactory->create($this->getFiles($filename, Dir::MODULE_ETC_DIR)); + return $this->getFilesIterator($filename, Dir::MODULE_ETC_DIR); } /** - * Go through all modules and find composer.json files of active modules + * Go through all modules and find composer.json files of active modules. * * @return FileIterator */ public function getComposerJsonFiles() { - return $this->fileIteratorFactory->create($this->getFiles('composer.json')); + return $this->getFilesIterator('composer.json'); + } + + /** + * Retrieve iterator for files with $filename from components located in component $subDir. + * + * @param string $filename + * @param string $subDir + * + * @return FileIterator + */ + private function getFilesIterator($filename, $subDir = '') + { + if (!isset($this->fileIterators[$subDir][$filename])) { + $this->fileIterators[$subDir][$filename] = $this->fileIteratorFactory->create( + $this->getFiles($filename, $subDir) + ); + } + return $this->fileIterators[$subDir][$filename]; } /** @@ -96,9 +121,9 @@ class Reader { $result = []; foreach ($this->modulesList->getNames() as $moduleName) { - $moduleEtcDir = $this->getModuleDir($subDir, $moduleName); - $file = $moduleEtcDir . '/' . $filename; - $directoryRead = $this->readFactory->create($moduleEtcDir); + $moduleSubDir = $this->getModuleDir($subDir, $moduleName); + $file = $moduleSubDir . '/' . $filename; + $directoryRead = $this->readFactory->create($moduleSubDir); $path = $directoryRead->getRelativePath($file); if ($directoryRead->isExist($path)) { $result[] = $file; @@ -159,5 +184,6 @@ class Reader public function setModuleDir($moduleName, $type, $path) { $this->customModuleDirs[$moduleName][$type] = $path; + $this->fileIterators = []; } } diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php index 65c0b090d6334a0ef6f6ffaa2a3c8b229389d073..3f577d9b3118755c88a575954c317d7ef140fb5e 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -140,17 +140,9 @@ class DiCompileCommand extends Command 'library' => $libraryPaths, 'generated_helpers' => $generationPath ]; - $excludedModulePaths = []; - foreach ($modulePaths as $appCodePath) { - $excludedModulePaths[] = '#^' . $appCodePath . '/Test#'; - } - $excludedLibraryPaths = []; - foreach ($libraryPaths as $libraryPath) { - $excludedLibraryPaths[] = '#^' . $libraryPath . '/([\\w]+/)?Test#'; - } $this->excludedPathsList = [ - 'application' => $excludedModulePaths, - 'framework' => $excludedLibraryPaths + 'application' => $this->getExcludedModulePaths($modulePaths), + 'framework' => $this->getExcludedLibraryPaths($libraryPaths), ]; $this->configureObjectManager($output); @@ -205,6 +197,54 @@ class DiCompileCommand extends Command } } + /** + * Build list of module path regexps which should be excluded from compilation + * + * @param string[] $modulePaths + * @return string[] + */ + private function getExcludedModulePaths(array $modulePaths) + { + $modulesByBasePath = []; + foreach ($modulePaths as $modulePath) { + $moduleDir = basename($modulePath); + $vendorPath = dirname($modulePath); + $vendorDir = basename($vendorPath); + $basePath = dirname($vendorPath); + $modulesByBasePath[$basePath][$vendorDir][] = $moduleDir; + } + + $basePathsRegExps = []; + foreach ($modulesByBasePath as $basePath => $vendorPaths) { + $vendorPathsRegExps = []; + foreach ($vendorPaths as $vendorDir => $vendorModules) { + $vendorPathsRegExps[] = $vendorDir + . '/(?:' . join('|', $vendorModules) . ')'; + } + $basePathsRegExps[] = $basePath + . '/(?:' . join('|', $vendorPathsRegExps) . ')'; + } + + $excludedModulePaths = [ + '#^(?:' . join('|', $basePathsRegExps) . ')/Test#', + ]; + return $excludedModulePaths; + } + + /** + * Build list of library path regexps which should be excluded from compilation + * + * @param string[] $libraryPaths + * @return string[] + */ + private function getExcludedLibraryPaths(array $libraryPaths) + { + $excludedLibraryPaths = [ + '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#', + ]; + return $excludedLibraryPaths; + } + /** * Delete directories by their code from "var" directory *