Hello Magento Friends,
Tier pricing is a valuable feature in Magento 2 that allows store owners to set different pricing levels based on the quantity purchased, encouraging bulk purchases by offering discounts. However, sometimes, the default tier pricing grid might not meet all the requirements, and adding custom columns to this grid becomes necessary.
This blog will guide you through the process of adding a custom column to the tier price section in Magento 2 Admin.
Steps to Add a Column to Tier Price in Magento 2 Admin:
Step 1: Create a db_schema.xml file using the path given below.
{{magento_root}}\app\code\Vendor\Extension\etc\db_schema.xml
Then add the following code
1 2 3 4 5 6 |
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="catalog_product_entity_tier_price" resource="default" engine="innodb" comment="Tier Pricing Table"> <column xsi:type="varchar" name="subscribesaveprice" nullable="true" length="255" comment="comment" /> </table> </schema> |
Step 2: Create a di.xml in the path given below.
{{magento_root}}\app\code\Vendor\Extension\etc\adminhtml\di.xml
Then add the code as given below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool"> <arguments> <argument name="modifiers" xsi:type="array"> <item name="customfield" xsi:type="array"> <item name="class" xsi:type="string">Vendor\Extension\Ui\DataProvider\Product\Form\Modifier\UpdateTierPricing</item> <item name="sortOrder" xsi:type="number">200</item> </item> </argument> </arguments> </virtualType> <type name="Vendor\Extension\Ui\DataProvider\Product\Form\Modifier\UpdateTierPricing"> <arguments> <argument name="scopeName" xsi:type="string">product_form.product_form</argument> </arguments> </type> </config> |
Step 3: Create a di.xml file in the path given below.
{{magento_root}}\app\code\Vendor\Extension\etc\di.xml
Now add the below-given code
1 2 3 4 5 6 7 8 |
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\UpdateHandler" type="Vendor\Extension\Model\Product\Attribute\Backend\TierPrice\UpdatePriceHandler" /> <preference for="Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler" type="Vendor\Extension\Model\Product\Attribute\Backend\TierPrice\SavePriceHandler" /> <preference for="Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice" type="Vendor\Extension\Model\ResourceModel\Product\Attribute\Backend\DataColumnUpdate" /> </config> |
Step 4: Create an UpdateTierPricing.php file in the path given below.
{{magento_root}}\app\code\Vendor\Extension\Ui\DataProvider\Product\Form\Modifier\UpdateTierPricing.php
Now include the code snippet as given below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
<?php namespace Vendor\Extension\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Ui\Component\Form\Field; use Magento\Ui\Component\Form\Fieldset; use Magento\Framework\Stdlib\ArrayManager; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Ui\Component\Container; use Magento\Ui\Component\Form\Element\DataType\Price; use Magento\Ui\Component\Form\Element\Input; use Magento\Ui\Component\Form\Element\Select; class UpdateTierPricing extends AbstractModifier { /** * @var ArrayManager * @since 101.0.0 */ protected $arrayManager; /** * @var string * @since 101.0.0 */ protected $scopeName; /** * @var array * @since 101.0.0 */ protected $meta = []; /** * UpdateTierPricing constructor. * @param ArrayManager $arrayManager */ public function __construct( ArrayManager $arrayManager ) { $this->arrayManager = $arrayManager; } /** * @param array $data * @return array * @since 100.1.0 */ public function modifyData(array $data) { // TODO: Implement modifyData() method. return $data; } /** * @param array $meta * @return array * @since 100.1.0 */ public function modifyMeta(array $meta) { // TODO: Implement modifyMeta() method. $this->meta = $meta; $this->customizeTierPrice(); return $this->meta; } /** * @return $this */ private function customizeTierPrice() { $tierPricePath = $this->arrayManager->findPath( ProductAttributeInterface::CODE_TIER_PRICE, $this->meta, null, 'children' ); if ($tierPricePath) { $this->meta = $this->arrayManager->merge( $tierPricePath, $this->meta, $this->getTierPriceStructure() ); } return $this; } private function getTierPriceStructure(){ return [ 'children' => [ 'record' => [ 'children' => [ 'subscribesaveprice' => [ 'arguments' => [ 'data' => [ 'config' => [ 'formElement' => Input::NAME, 'componentType' => Field::NAME, 'dataType' => Price::NAME, 'label' => __('Custom Column'), 'dataScope' => 'subscribesaveprice', 'sortOrder' => '100' ], ], ], ], ], ], ], ]; } } |
Step 5: Create a SaveHandler.php file in path given below.
{{magento_root}}\app\code\Vendor\Extension\Model\Product\Attribute\Backend\TierPrice\SavePriceHandler.php
Now add the code as follows
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php namespace Vendor\Extension\Model\Product\Attribute\Backend\TierPrice; use Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler; class SavePriceHandler extends SaveHandler { /** * Get additional tier price fields. * * @param array $objectArray * @return array */ public function getAdditionalFields(array $objectArray): array { $percentageValue = $this->getPercentage($objectArray); return [ 'value' => $percentageValue ? null : $objectArray['price'], 'percentage_value' => $percentageValue ?: null, 'subscribesaveprice' => $this->getSecondaryUnit($objectArray), ]; } /** * @param array $priceRow * @return mixed|null */ public function getSecondaryUnit(array $priceRow) { return isset($priceRow['subscribesaveprice']) && !empty($priceRow['subscribesaveprice']) ? $priceRow['subscribesaveprice'] : null; } } |
Step 6: Create a UpdatePriceHandler.php file in the given below path.
{{magento_root}}\app\code\Vendor\Extension\Model\UpdatePriceHandler.php
Then add the following code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ declare(strict_types=1); namespace Vendor\Extension\Model\Product\Attribute\Backend\TierPrice; use Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\UpdateHandler; use Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\AbstractHandler; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Locale\FormatInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice; /** * Process tier price data for handled existing product. */ class UpdatePriceHandler extends AbstractHandler { /** * @var \Magento\Store\Model\StoreManagerInterface */ private $storeManager; /** * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface */ private $attributeRepository; /** * @var \Magento\Framework\EntityManager\MetadataPool */ private $metadataPoll; /** * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice */ private $tierPriceResource; /** * @var FormatInterface */ private $localeFormat; /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $tierPriceResource * @param FormatInterface|null $localeFormat */ public function __construct( StoreManagerInterface $storeManager, ProductAttributeRepositoryInterface $attributeRepository, GroupManagementInterface $groupManagement, MetadataPool $metadataPool, Tierprice $tierPriceResource, FormatInterface $localeFormat = null ) { parent::__construct($groupManagement); $this->storeManager = $storeManager; $this->attributeRepository = $attributeRepository; $this->metadataPoll = $metadataPool; $this->tierPriceResource = $tierPriceResource; $this->localeFormat = $localeFormat ?: ObjectManager::getInstance()->get(FormatInterface::class); } /** * Perform action on relation/extension attribute. * * @param \Magento\Catalog\Api\Data\ProductInterface|object $entity * @param array $arguments * @return \Magento\Catalog\Api\Data\ProductInterface|object * @throws \Magento\Framework\Exception\InputException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entity, $arguments = []) { $attribute = $this->attributeRepository->get('tier_price'); $priceRows = $entity->getData($attribute->getName()); if (null !== $priceRows) { if (!is_array($priceRows)) { throw new \Magento\Framework\Exception\InputException( __('Tier prices data should be array, but actually other type is received') ); } $websiteId = (int)$this->storeManager->getStore($entity->getStoreId())->getWebsiteId(); $isGlobal = $attribute->isScopeGlobal() || $websiteId === 0; $identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField(); $productId = (int)$entity->getData($identifierField); // prepare original data to compare $origPrices = $entity->getOrigData($attribute->getName()); $old = $this->prepareOldTierPriceToCompare($origPrices); // prepare data for save $new = $this->prepareNewDataForSave($priceRows, $isGlobal); $delete = array_diff_key($old, $new); $insert = array_diff_key($new, $old); $update = array_intersect_key($new, $old); $isAttributeChanged = $this->deleteValues($productId, $delete); $isAttributeChanged |= $this->insertValues($productId, $insert); $isAttributeChanged |= $this->updateValues($update, $old); if ($isAttributeChanged) { $valueChangedKey = $attribute->getName() . '_changed'; $entity->setData($valueChangedKey, 1); } } return $entity; } /** * Update existing tier prices for processed product * * @param array $valuesToUpdate * @param array $oldValues * @return bool */ public function updateValues(array $valuesToUpdate, array $oldValues): bool { $isChanged = false; foreach ($valuesToUpdate as $key => $value) { if ((!empty($value['value']) && (float)$oldValues[$key]['price'] !== (float)$value['value']) || $this->getPercentage($oldValues[$key]) !== $this->getPercentage($value) || $this->getSecondaryUnit($oldValues[$key]) !== $this->getSecondaryUnit($value) ) { $price = new \Magento\Framework\DataObject( [ 'value_id' => $oldValues[$key]['price_id'], 'value' => $value['value'], 'percentage_value' => $this->getPercentage($value), 'subscribesaveprice' => $this->getSecondaryUnit($value), ] ); $this->tierPriceResource->savePriceData($price); $isChanged = true; } } return $isChanged; } /** * Get additional tier price fields. * * @param array $objectArray * @return array */ public function getAdditionalFields(array $objectArray): array { $percentageValue = $this->getPercentage($objectArray); return [ 'value' => $percentageValue ? null : $objectArray['price'], 'percentage_value' => $percentageValue ?: null, 'subscribesaveprice' => $this->getSecondaryUnit($objectArray), ]; } /** * @param array $priceRow * @return mixed|null */ public function getSecondaryUnit(array $priceRow) { return isset($priceRow['subscribesaveprice']) && !empty($priceRow['subscribesaveprice']) ? $priceRow['subscribesaveprice'] : null; } /** * Insert new tier prices for processed product * * @param int $productId * @param array $valuesToInsert * @return bool */ private function insertValues(int $productId, array $valuesToInsert): bool { $isChanged = false; $identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField(); foreach ($valuesToInsert as $data) { $price = new \Magento\Framework\DataObject($data); $price->setData( $identifierField, $productId ); $this->tierPriceResource->savePriceData($price); $isChanged = true; } return $isChanged; } /** * Delete tier price values for processed product * * @param int $productId * @param array $valuesToDelete * @return bool */ private function deleteValues(int $productId, array $valuesToDelete): bool { $isChanged = false; foreach ($valuesToDelete as $data) { $this->tierPriceResource->deletePriceData($productId, null, $data['price_id']); $isChanged = true; } return $isChanged; } /** * Get generated price key based on price data * * @param array $priceData * @return string */ private function getPriceKey(array $priceData): string { $qty = $this->parseQty($priceData['price_qty']); $key = implode( '-', array_merge([$priceData['website_id'], $priceData['cust_group']], [$qty]) ); return $key; } /** * Check by id is website global * * @param int $websiteId * @return bool */ private function isWebsiteGlobal(int $websiteId): bool { return $websiteId === 0; } /** * Prepare old data to compare. * * @param array|null $origPrices * @return array */ private function prepareOldTierPriceToCompare(?array $origPrices): array { $old = []; if (is_array($origPrices)) { foreach ($origPrices as $data) { $key = $this->getPriceKey($data); $old[$key] = $data; } } return $old; } /** * Prepare new data for save. * * @param array $priceRows * @param bool $isGlobal * @return array * @throws \Magento\Framework\Exception\LocalizedException */ private function prepareNewDataForSave(array $priceRows, bool $isGlobal = true): array { $new = []; $priceRows = array_filter($priceRows); foreach ($priceRows as $data) { if (empty($data['delete']) && (!empty($data['price_qty']) || isset($data['cust_group']) || $isGlobal === $this->isWebsiteGlobal((int)$data['website_id'])) ) { $key = $this->getPriceKey($data); $new[$key] = $this->prepareTierPrice($data); } } return $new; } } |
Step 7: Create a DataColumnUpdate.php file in path given below.
{{magento_root}}\app\code\Vendor\Extension\Model\ResourceModel\Product\Attribute\Backend\DataColumnUpdate.php
Now include the following code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php namespace Vendor\Extension\Model\ResourceModel\Product\Attribute\Backend; use Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice; class DataColumnUpdate extends Tierprice { /** * @param array $columns * @return array */ protected function _loadPriceDataColumns($columns) { $columns = parent::_loadPriceDataColumns($columns); $columns['subscribesaveprice'] = 'subscribesaveprice'; return $columns; } } |
Step 8: Create a catalog_product_prices.xml file in path given below.
{{magento_root}}\app\code\Vendor\Extension\view\base\layout\catalog_product_prices.xml
After that, add the below-mentioned code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0"?> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <referenceBlock name="render.product.prices"> <arguments> <argument name="default" xsi:type="array"> <item name="prices" xsi:type="array"> <item name="tier_price" xsi:type="array"> <item name="render_template" xsi:type="string">Vendor_Extension::product/price/tier_price.phtml</item> </item> </item> </argument> </arguments> </referenceBlock> </layout> |
Step 9: Create a tier_price.phtml file in path given below.
{{magento_root}}\app\code\Vendor\Extension\view\base\templates\product\price\tier_price.phtml
Now add the following code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ ?> <?php // phpcs:disable Magento2.Templates.ThisInTemplate // phpcs:disable Generic.WhiteSpace.ScopeIndent /** @var \Magento\Catalog\Pricing\Render\PriceBox $block */ /** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ /** @var \Magento\Framework\Locale\LocaleFormatter $localeFormatter*/ /** @var \Magento\Catalog\Pricing\Price\TierPrice $tierPriceModel */ $tierPriceModel = $block->getPrice(); $tierPrices = $tierPriceModel->getTierPriceList(); $msrpShowOnGesture = $block->getPriceType('msrp_price')->isShowPriceOnGesture(); $product = $block->getSaleableItem(); ?> <?php if (count($tierPrices)): ?> <ul class="<?= $block->escapeHtmlAttr(($block->hasListClass() ? $block->getListClass(): 'prices-tier items')) ?>"> <?php foreach ($tierPrices as $index => $price): ?> <li class="item"> <?php $productId = $product->getId(); $isSaleable = $product->isSaleable(); $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20); if ($msrpShowOnGesture && $price['price']->getValue() < $product->getMsrp()): $addToCartUrl = ''; if ($isSaleable) { $addToCartUrl = $this->helper(\Magento\Checkout\Helper\Cart::class) ->getAddUrl($product, ['qty' => $price['price_qty']]); } $tierPriceData = [ 'addToCartUrl' => $addToCartUrl, 'name' => $product->getName(), 'realPrice' => $block->renderAmount( $price['price'], [ 'price_id' => $index, 'id_suffix' => '-' . $index, 'include_container' => true ] ), 'msrpPrice' => $block->renderAmount( $block->getPriceType('msrp_price')->getAmount(), [ 'price_id' => $index, 'id_suffix' => '-' . $index, 'include_container' => true ] ), ]; if ($block->getCanDisplayQty($product)) { $tierPriceData['qty'] = $price['price_qty']; } ?> <?= $block->escapeHtml(__('Buy %1 for: ', $price['price_qty'])) ?> <a href="#" id="<?= $block->escapeHtmlAttr($popupId) ?>" data-tier-price="<?= $block->escapeHtml($block->jsonEncode($tierPriceData)) ?>"> <?= $block->escapeHtml(__('Click for price')) ?> </a> <?= /* @noEscape */ $secureRenderer->renderEventListenerAsTag( 'onclick', 'event.preventDefault()', 'a#' . $block->escapeHtmlAttr($popupId) ) ?> <?php else: $priceAmountBlock = $block->renderAmount( $price['price'], [ 'price_id' => $index, 'id_suffix' => '-' . $index, 'include_container' => true, 'zone' => \Magento\Framework\Pricing\Render::ZONE_ITEM_OPTION ] ); ?> <?= /* @noEscape */ ($block->getShowDetailedPrice() !== false) ? __( 'Buy %1 for %2 each and '. '<strong class="benefit">save<span class="percent tier-%3"> %4</span>%</strong> %5 ', $localeFormatter->formatNumber($price['price_qty']), $priceAmountBlock, $index, $localeFormatter->formatNumber( $block->formatPercent($tierPriceModel->getSavePercent($price['price'])) ), $localeFormatter->formatNumber($price['subscribesaveprice']) ) : __('Buy %1 for %2 each', $price['price_qty'], $priceAmountBlock); ?> <?php endif; ?> </li> <?php endforeach; ?> </ul> <?php if ($msrpShowOnGesture):?> <script type="text/x-magento-init"> { ".product-info-main": { "addToCart": { "origin": "tier", "addToCartButton": "#product_addtocart_form [type=submit]", "inputQty": "#qty", "attr": "[data-tier-price]", "productForm": "#product_addtocart_form", "productId": "<?= (int) $productId ?>", "productIdInput": "input[type=hidden][name=product]", "isSaleable": "<?= (bool) $isSaleable ?>" } } } </script> <?php endif;?> <?php endif; ?> |
Output:
The custom column will be added to the admin grid for tier pricing.
Custom column content of tier price is displayed in Magento 2 frontend.
Conclusion:
By following these steps, you can easily add a custom column to the tier price grid in the Magento 2 Admin.
Related Tutorials –
- How to Display Tier Price at Category Page in Magento 2
- How to Setup Tier Price in Magento 2
- Everything that You Have to Know About Tier Price for Configurable Products in Magento 2
- How to Set Product Tier Price Programmatically in Magento 2