-
-
Notifications
You must be signed in to change notification settings - Fork 2
sales_order_grid
Tested against Magento 2.3.5.
- How to add a new column to any and all grids in the admin?
- Add a new column
- Different methods of populating it
- Making it searchable/filterable
- Making it sortable
Basically, to add a column to sales order grid you have to perform 3 simple steps:
- Add a column to
sales_order_grid
database table - Add DI configuration to populate the column in
sales_order_grid
table with your value - Add UI component’s configuration to display the column in the grid
Sales order grid in Magento 2 can be accessed in the admin panel under “Sales” -> “Orders” menu. By default, only several main columns are visible in grid, but there are additional columns, that can be enabled from “Columns” dropdown on the top-right side.
However, if you are creating a module that provides additional useful information about orders, it is a good idea to add corresponding columns to the sales order grid.
Basically, to add a column to sales order grid you have to perform 3 simple steps:
- Add a column to
sales_order_grid
database table - Add DI configuration to populate the column in
sales_order_grid
table with your value - Add UI component’s configuration to display the column in the grid
Let’s go through this implementation step by step.
First of all, ensure you have your column in the database table, and it is mapped to any field of sales_order
table. For my example, let’s assume that there is the pavingdirect_ordertypes_order_to_order_type
table consisting of two columns: order_id
and order_type_id
. This table is used to store some order type related to the particular order. It would be nice to display this information in the “Order Type” column in the sales order grid. Let’s do this!
Columns are added to database tables using InstallSchema script. To be consistent, this script should be updated in the same module, where the pavingdirect_ordertypes_order_to_order_type
table was added.
The following code fragment will do the trick:
$setup->getConnection()->addColumn(
$setup->getTable('sales_order_grid'),
'order_type_id',
[
'type' => Table::TYPE_TEXT,
'comment' => 'Order Type'
]
);
Just add it to app/code/<your_namespace>/<your_module>/Setup/InstallSchema.php
file and it will create a column named order_type_id
, of type text, with a comment “Order Type” to sales_order_grid
during installation.
Don’t worry about the implications of core table modification. sales_order_grid
is index table and is used for order grid rendering speed up. It is designed to store all information required for sales order grid rendering, so custom columns are required to be added to this table.
To reflect changes in the database, a Magento reinstallation is required. Optionally, deleting module entry from setup_module table and running bin/magento setup:upgrade
command will be also ok.
After this step, order_type_id
column is present in sales_order_grid
table, but is remaining empty as it is not mapped to any data source.
DI configuration to populate the column is sales_order_grid
table.
At this stage, it would be good to understand how sales_order_grid
table is populated.
When an order is placed (according to default configuration), data related to this order is selected from sales_order
table joining several additional tables and inserted to sales_order_grid
. This operation is initiated by the \Magento\Sales\Model\ResourceModel\Grid::refresh
function and the default select is declared in “<Magento Sales module>/etc/di.xml
” file.
So to include our table in the mentioned insert from select, we have to extend di configuration creating the app/code/<Namespace>/<Module>/etc/adminhtml/di.xml
file.
The following XML snippet should be added to di configuration inside the config node.
<config ...>
...
<virtualType name="Magento\Sales\Model\ResourceModel\Order\Grid">
<arguments>
<argument name="joins" xsi:type="array">
<item name="pavingdirect_ordertypes_order_to_order_type" xsi:type="array">
<item name="table" xsi:type="string">pavingdirect_ordertypes_order_to_order_type</item>
<item name="origin_column" xsi:type="string">entity_id</item>
<item name="target_column" xsi:type="string">order_id</item>
</item>
</argument>
<argument name="columns" xsi:type="array">
<item name="order_type_id" xsi:type="string">pavingdirect_ordertypes_order_to_order_type.order_type_id</item>
</argument>
</arguments>
</virtualType>
...
</config>
This configuration is specifying that pavingdirect_ordertypes_order_to_order_type
table will be joined to select from sales_order
on sales_order.entity_id = pavingdirect_ordertypes_order_to_order_type.order_id
and will populate sales_order_grid.order_type_id
column with corresponding value from pavingdirect_ordertypes_order_to_order_type.order_type_id
.
After this step, our order_type_id
column in sales_order_grid
table is populated with the value from pavingdirect_ordertypes_order_to_order_type
table each time order is placed. Still, a column will exist only in the database, and will not be visible in the admin panel.
Finally, to reflect the column on admin panel grid, we have to extend the sales_order_grid
UI component by adding a UI configuration file in our module.
It is possible to extend UI configuration for sales order grid introducing the app/code/<Namespace>/<Module>/view/adminhtml/ui_component/sales_order_grid.xml
file. Basically, it should have the same name and path in relation to module directory as the main sales order grid UI component file: app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml
.
Put the following XML snippet to the UI configuration file:
<listing ...>
<columns name="sales_order_columns">
<column name="order_type_id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Order Type</item>
</item>
</argument>
</column>
</columns>
</listing>
This will extend sales_order_columns
and add a column based on order_type_id
field, of type text, with a translatable label “Order Type”.
There are two approaches to ensure that the correct value is present in the source table when sales_order_grid
is populated:
- Save value to source table before
sales_order_grid
refresh is triggered. - Trigger
sales_order_grid
refresh after saving value.
The first approach is necessary if a value should be saved to source table right on place order action. One of the solutions will be to create an observer on the event that is dispatched when an order is saved, but the grid is still not refreshed. That event is sales_order_save_after
.
The second approach is applicable when the source table is updated sometime after an order is placed, or asynchronously. Here you have to call the \Magento\Sales\Model\ResourceModel\Grid::refresh
method after a value is saved to the source table.
For upgrading existing data either install (for the first release of your module) or upgrade script should be created.
Here is an example of an upgrade script that is populating the sales_order_grid.order_type_id
column from the pavingdirect_ordertypes_order_to_order_type.order_type_id
column for 1.0.1
version of the module.
For this upgrade script to be executed, you have to increase module version in module.xml
(from 1.0.0
to 1.0.1
) and run the bin/magento setup:upgrade
command.
Be sure to refresh config cache after editing XML files.
The settings described here are only the basics, actually, there are much more parameters available for install script, DI and UI configuration to customize content and appearance of the added column, take time to observe existing install scripts, DI and UI configuration files and try adding more parameters to your implementation.
Originally published at www.sivaschenko.com.
- On order submit in admin,
sales_order_process_relation
event is triggered - Which calls
\Magento\Sales\Model\ResourceModel\Grid::refresh
. - Which generates an
INSERT INTO sales_order_grid SELECT ...
SQL query, which populates thesales_order_grid
table - The
SELECT
part of that query looks like this:
SELECT sales_order.entity_id AS `entity_id`,
sales_order.status AS `status`,
sales_order.store_id AS `store_id`,
sales_order.store_name AS `store_name`,
sales_order.customer_id AS `customer_id`,
sales_order.base_grand_total AS `base_grand_total`,
sales_order.base_total_paid AS `base_total_paid`,
sales_order.grand_total AS `grand_total`,
sales_order.total_paid AS `total_paid`,
sales_order.increment_id AS `increment_id`,
sales_order.base_currency_code AS `base_currency_code`,
sales_order.order_currency_code AS `order_currency_code`,
TRIM(CONCAT_WS(' ', IF(`sales_shipping_address`.`firstname` <> '', `sales_shipping_address`.`firstname`, NULL),
IF(`sales_shipping_address`.`lastname` <> '', `sales_shipping_address`.`lastname`,
NULL))) AS `shipping_name`,
TRIM(CONCAT_WS(' ', IF(`sales_billing_address`.`firstname` <> '', `sales_billing_address`.`firstname`, NULL),
IF(`sales_billing_address`.`lastname` <> '', `sales_billing_address`.`lastname`,
NULL))) AS `billing_name`,
sales_order.created_at AS `created_at`,
sales_order.updated_at AS `updated_at`,
TRIM(CONCAT_WS(' ', IF(`sales_billing_address`.`company` <> '', `sales_billing_address`.`company`, NULL),
IF(`sales_billing_address`.`street` <> '', `sales_billing_address`.`street`, NULL),
IF(`sales_billing_address`.`city` <> '', `sales_billing_address`.`city`, NULL),
IF(`sales_billing_address`.`region` <> '', `sales_billing_address`.`region`, NULL),
IF(`sales_billing_address`.`postcode` <> '', `sales_billing_address`.`postcode`,
NULL))) AS `billing_address`,
TRIM(CONCAT_WS(' ', IF(`sales_shipping_address`.`company` <> '', `sales_shipping_address`.`company`, NULL),
IF(`sales_shipping_address`.`street` <> '', `sales_shipping_address`.`street`, NULL),
IF(`sales_shipping_address`.`city` <> '', `sales_shipping_address`.`city`, NULL),
IF(`sales_shipping_address`.`region` <> '', `sales_shipping_address`.`region`, NULL),
IF(`sales_shipping_address`.`postcode` <> '', `sales_shipping_address`.`postcode`,
NULL))) AS `shipping_address`,
sales_order.shipping_description AS `shipping_information`,
sales_order.customer_email AS `customer_email`,
sales_order.customer_group_id AS `customer_group`,
sales_order.base_subtotal AS `subtotal`,
sales_order.base_shipping_amount AS `shipping_and_handling`,
TRIM(CONCAT_WS(' ', IF(`sales_order`.`customer_firstname` <> '', `sales_order`.`customer_firstname`, NULL),
IF(`sales_order`.`customer_lastname` <> '', `sales_order`.`customer_lastname`,
NULL))) AS `customer_name`,
sales_order_payment.method AS `payment_method`,
sales_order.total_refunded AS `total_refunded`,
sales_order.admin_user AS `admin_user`,
sales_order.delivery_date AS `delivery_date`,
sales_order.delivery_slot AS `delivery_slot`,
pavingdirect_ordertypes_order_to_order_type.order_type_id AS `order_type_id`
FROM `sales_order`
LEFT JOIN `sales_order_address` AS `sales_shipping_address`
ON sales_order.shipping_address_id = sales_shipping_address.entity_id
LEFT JOIN `sales_order_address` AS `sales_billing_address`
ON sales_order.billing_address_id = sales_billing_address.entity_id
LEFT JOIN `sales_order_payment` ON sales_order.entity_id = sales_order_payment.parent_id
LEFT JOIN `pavingdirect_ordertypes_order_to_order_type` AS `order_type_id`
ON sales_order.entity_id = order_type_id.order_id
Which is pretty gnarly.
- The
LEFT JOINS
in there are defined in XML (of course). Most of them are defined in the core file<Magento_Sales>/etc/di.xml
- This file must be in the global area (i.e.
etc/di.xml
) because that is where the version inMagento_Sales
module is defined and which our XML will be merged into. If it is located inetc/adminhtml/di.xml
, then the XML from<Magento_Sales>/etc/di.xml
won't be merged in (different app area!). When creating an order, thesales_order_process_relation
event is dispatched, which tries to update thesales_order_grid
by generating an SQL query to do so. Running this query will trigger an SQL error because the data from the other joins needed to populate thesales_order_grid
table won't be present. - The column_alias (
order_type_id
in this example) must be the exact name of the column in sales_order_grid, otherwise the SQL query generated to populate that table will fail, because theINSERT INTO sales_order_grid (order_type_id)
andON DUPLICATE KEY UPDATE order_type_id = VALUES(order_type_id)
parts of the query do not accept aliases, only the original column names for the destination table (sales_order_grid in this example). It doesn't matter whether MySQL actually supports this, because the M2 code (which generates this part of the query) doesn't. So a query like this will work, but cannot be generated by M2:
# Manually corrected query, confirmed working:
INSERT INTO `sales_order_grid` (`order_type_id`)
SELECT order_type_alias.order_type_id AS `order_type_id_alias`
FROM `sales_order`
LEFT JOIN `pavingdirect_ordertypes_order_to_order_type` AS `order_type_alias`
ON sales_order.entity_id = order_type_alias.order_id
WHERE (sales_order.entity_id = '4384')
ON DUPLICATE KEY UPDATE order_type_id = VALUES(`order_type_id`);
Magento instead generates something like this, which is broken:
INSERT INTO `sales_order_grid` (`order_type_id_alias`)
SELECT order_type_alias.order_type_id AS `order_type_id_alias`
FROM `sales_order`
LEFT JOIN `pavingdirect_ordertypes_order_to_order_type` AS `order_type_alias`
ON sales_order.entity_id = order_type_alias.order_id
WHERE (sales_order.entity_id = '4384')
ON DUPLICATE KEY UPDATE order_type_id_alias = VALUES(`order_type_id_alias`);
# Column order_type_id_alias does not exist in sales_order_grid, so this query will fail with SQLSTATE[42S22]: Column not found: 1054 Unknown column 'order_type_id_alias' in 'field list' error
- sales_order_place_after
- sales_model_service_quote_submit_success
$this->eventManager->dispatch('checkout_submit_all_after', ['order' => $order, 'quote' => $quote]);
$this->eventManager->dispatch('sales_model_service_quote_submit_success',['order' => $order, 'quote' => $quote]);
$this->_eventManager->dispatch('sales_order_place_after', ['order' => $this]);
$this->eventManager->dispatch('sales_order_process_relation',['object' => $object]);
- What is the role of the cron job
sales_grid_order_async_insert
?
- https://magento.stackexchange.com/questions/134754/magento-2-how-to-add-a-new-column-to-orders-grid#134890
- https://medium.com/@sivaschenko/magento-2-adding-a-column-to-the-sales-order-grid-fca2f62c0b0c
- https://gist.github.com/sivaschenko/ff1e9e8f90f082b8d3e83415aa63082f#file-blog-magento2-sales-order-grid-column-upgrade-script-php