If you have ever googled “magento advanced pricing customer group price website field disabled” or “magento customer group price disabled for store scope,” this blog post is for you. The lack of results inspired me to look under the hood and, therefore, write about my findings.
The Story Behind Googling
Why was I even googling this? On one of our projects (two websites, each with one store view), I was setting up customer group prices for a small subset of products. Customer group pricing pretty much works like tier pricing; the only difference is that customer group prices have a quantity of 1.
During the price setup, I didn’t pay much attention to scope since the customer group price form has <select>
elements for the website. Advanced pricing modal (where the customer group price is set) looked something like this:
Everything looked and worked fine from both the admin and the front end. I informed the client that I had set the customer group prices and they were ready for testing on staging, along with other agreed-upon improvements. The client responded with a screenshot similar to this, with the message that adding customer group prices is disabled for the website’s scope.
Checking with Default Magento Installation
At first, I’ve thought this was a bug since it worked on default Magento with sample data. I’ve checked our modules and 3rd party extensions that have something to do with rendering of elements on the admin product edit page. At that moment, I was unaware of the main prerequisite for this scenario:
catalog/price/scope
config being set to website
Keep in mind that default Magento installation has this config set to global
.
Setting Scope on tier_price Attribute
Few debugging hours later, here’s what I came up with while inspecting admin product edit form loading. This is the story of tier_price
product attribute and (non) global
scope which looks something like this:
(...)
MagentoCatalogModelProductAttributeBackendPrice->setAttribute()
MagentoCatalogModelProductAttributeBackendPrice->setScope()
public function setScope($attribute) // $attribute is tier_price
{
// checks catalog/price/scope config, in our case scope is 'website'
if ($this->_helper->isPriceGlobal()) {
$attribute->setIsGlobal(ScopedAttributeInterface::SCOPE_GLOBAL);
} else {
// attribute's scope is set to 'website'
$attribute->setIsGlobal(ScopedAttributeInterface::SCOPE_WEBSITE);
}
return $this;
}
(...)
Later, when product attributes are prepared for form rendering in
MagentoCatalogUiDataProviderProductFormModifierEav
the tier_price
attribute is (among the others) checked for scope, is it global or not.
Detective Mode Enabled: In Search for ‘disabled’ Property Culprit
If the catalog/price/scope
is set to global
(and therefore the tier_price
attribute scope), part of the code where the disabled property is set is never executed. That’s why the “Add” button from the Customer Group Price form wasn’t disabled when I was checking the reported problem on the default installation. This is the part of product form configuration responsible for customer group price container:
MagentoCatalogUiDataProviderProductFormProductDataProvider->getMeta() // returns $meta array with product form configuration
// part of $meta array which is the reason this debugging looks like this
// (printed out in JSON format for easier readability)
{
"arguments": {
"data": {
"config": {
"formElement": "container",
"componentType": "container",
"breakLine": false,
"label": "Tier Price",
"required": "0",
"sortOrder": 40
}
}
},
"children": {
"tier_price": {
"arguments": {
"data": {
"config": {
"dataType": "text",
"formElement": "input",
"visible": "1",
"required": "0",
"notice": null,
"default": null,
"label": "Tier Price",
"code": "tier_price",
"source": "advanced-pricing",
"scopeLabel": "[WEBSITE]",
"globalScope": false,
"sortOrder": 40,
"service": {
"template": "ui/form/element/helper/service"
},
"componentType": "field",
"disabled": true -> causes “I’m disabled” meme
}
}
}
}
}
}
Who and where says that tier_price
element is disabled? Let’s take a look at stack trace and responsible methods (pay attention to comments!):
(...)
// group of interest is advanced-pricing {MagentoEavModelEntityAttributeGroup}
MagentoCatalogUiDataProviderProductFormModifierEav->modifyMeta()
// here we're "catching" tier_price attribute
MagentoCatalogUiDataProviderProductFormModifierEav->getAttributesMeta()
MagentoCatalogUiDataProviderProductFormModifierEav->addContainerChildren()
// attribute_code = tier_price, $groupCode = advanced-pricing, $sortOrder = 4
MagentoCatalogUiDataProviderProductFormModifierEav->getContainerChildren(ProductAttributeInterface $attribute, $groupCode, $sortOrder)
MagentoCatalogUiDataProviderProductFormModifierEav->setupAttributeMeta()
MagentoCatalogUiDataProviderProductFormModifierEav->addUseDefaultValueCheckbox()
private function addUseDefaultValueCheckbox(ProductAttributeInterface $attribute, array $meta)
{
/**
* $canDisplayService = false if catalog/price/scope is global
* so 'disabled' property is never set in this case
*/
$canDisplayService = $this->canDisplayUseDefault($attribute);
if ($canDisplayService) {
$meta['arguments']['data']['config']['service'] = [
'template' => 'ui/form/element/helper/service',
];
/**
* This evaluates to !false = true so 'disabled' property for tier_price element in
* Customer Group Price form is set to true.
* Answer to “Who and where says that tier_price element is disabled?” It's HERE!
*/
$meta['arguments']['data']['config']['disabled'] =
!$this->scopeOverriddenValue->containsValue(
MagentoCatalogApiDataProductInterface::class,
$this->locator->getProduct(),
$attribute->getAttributeCode(),
$this->locator->getStore()->getId()
);
}
return $meta;
}
-----------------------------------------------------------------------------------------------------------
private function canDisplayUseDefault(ProductAttributeInterface $attribute)
{
$attributeCode = $attribute->getAttributeCode();
/** @var Product $product */
$product = $this->locator->getProduct();
if ($product->isLockedAttribute($attributeCode)) {
return false;
}
if (isset($this->canDisplayUseDefault[$attributeCode])) {
return $this->canDisplayUseDefault[$attributeCode];
}
// $attribute is tier_price, and it's scope depends on catalog/price/scope
return $this->canDisplayUseDefault[$attributeCode] = (
// scope 'website' != scope 'global'
($attribute->getScope() != ProductAttributeInterface::SCOPE_GLOBAL_TEXT)
&& $product
&& $product->getId()
&& $product->getStoreId()
);
}
Workaround for Enabling the Add Button
However, since the client explicitly requested to be able to edit Customer Group Price while on store view scope, I’ve prepared a workaround in the form of a plugin. Create a new module (mine is Inchoo_CustomerGroupPrice
) and add below code to your etc/di.xml
file.
<?xml version="1.0"?>
<config xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="MagentoCatalogUiDataProviderProductFormModifierAdvancedPricing">
<plugin name="advanced_pricing_customer_group_price_plugin"
type="InchooCustomerGroupPricePluginCatalogUiDataProviderProductFormModifierAdvancedPricingPlugin"/>
</type>
</config>
AdvancedPricingPlugin
checks $meta
array that contains necessary information about tier_price
form element and forces enabling of that element. Here’s the implementation:
<?php
declare(strict_types=1);
namespace InchooCustomerGroupPricePluginCatalogUiDataProviderProductFormModifier;
use MagentoCatalogUiDataProviderProductFormModifierAdvancedPricing;
use MagentoFrameworkStdlibArrayManager;
class AdvancedPricingPlugin
{
/**
* @param ArrayManager $arrayManager
*/
public function __construct(protected ArrayManager $arrayManager)
{
}
/**
* @param AdvancedPricing $subject
* @param array $meta
* @return array
*/
public function afterModifyMeta(AdvancedPricing $subject, array $meta): array
{
$path = 'advanced_pricing_modal/children/advanced-pricing/children/tier_price/arguments/data/config/disabled';
if ($this->arrayManager->get($path, $meta) === true) {
$meta = $this->arrayManager->set($path, $meta, false);
}
return $meta;
}
}
Remember to flush the cache before testing the plugin! The plugin’s execution should result in the customer group price being enabled for editing while being on store scope in the Magento admin dashboard.
A Feature or a Bug?
Enabling customer group price for website scope is a specific problem, i.e. subject (it’s a feature, not a bug! ). I hope that it will help someone, at least at some point, not to lose time for something that is simply related to Magento configuration and works as it should. It’s much quicker when you know where to look at.