[Mini-Tutorial] How to Add a Customer Order Grid to a Product Tab in the Adminhtml

(or “Putting Things Where They Don’t Belong”)

Modifying the Magento Admin Panel can be an intimidating task. Recently, we were asked to come up with a way of displaying a list of customers for products purchased. We had devised some custom functionality for displaying on a product, within the admin panel, a list of customers who had purchased said product. This information was to be gathered and displayed only for certain products belonging to a particular attribute set.

In tackling this exercise, I added a grid display to a newly added tab on the product view of the admin panel to output this list of customers. It was fully functional with sorting and filtering through Ajax, and included the ability for CSV export and some mass actions.

Outside of this greater functionality, I feel the ability to view relevant customer order information while viewing a product is a valuable asset, providing relevant metrics at a glance for storeowners and customer service representatives. Moreover, developers can adapt this technique to display a grid containing any relevant collection of information on to any form tab of the Magento admin panel desired.

In this tutorial, I will be demonstrating how to implement this functionality of displaying a customer order grid on a product tab. The final module is included at the end of this post for your convenience.

Related: How to Set Up Your Magento Store

Example Scenario

Let us presume that a customer purchases the following product – a pair of Aviator Sunglasses for $295.00:

Aviator Sunglasses

If we examine their order through the Magento admin panel, we can see that this customer (Linda Wood) purchased five such sunglasses in her order for an item subtotal of $1,475.00.

#100000094 _ Orders _ Sales _ Magento Admin

Now, what if we want to have access to this order information when viewing the product itself in the admin panel?

Related: Creating Canadian Tax Rules in Magento

Tutorial

Let us start by adding a new tab to the product view of the admin panel by creating a rewrite of the Mage_Adminhtml_Block_Catalog_Product_Edit_Tabs class.

<?php
class Demac_ProductOrders_Block_Adminhtml_Catalog_Product_Edit_Tabs extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tabs
{
/**
* @var
*/
private $parent;
/**
* @return mixed Mage_Core_Block_Abstract
*/
protected function _prepareLayout()
{
// get all existing tabs
$this->parent = parent::_prepareLayout();
// add new tab
$this->addTab('orders', array(
'label' => Mage::helper('catalog')->__('Orders'),
'content' => $this->getLayout()->createBlock('demac_productorders/adminhtml_catalog_product_edit_tab_orders')->toHtml(),
));
return $this->parent;
}
}

Here, we’re adding a new tab to the existing set that will make use of the following block at Demac_ProductOrders_Block_Adminhtml_Catalog_Product_Edit_Tab_Orders.

<?php
class Demac_ProductOrders_Block_Adminhtml_Catalog_Product_Edit_Tab_Orders extends Mage_Adminhtml_Block_Widget_Grid
{
public function __construct()
{
parent::__construct();
$this->setId('productordersGrid');
$this->setUseAjax(true);
$this->setDefaultSort('created_at');
$this->setDefaultDir('DESC');
$this->setSaveParametersInSession(true);
}
/**
* Get the current product
*
* @return mixed Mage_Catalog_Model_Product
*
*/
protected function _getProduct()
{
return Mage::registry('current_product');
}
/**
* Prepare grid collection object
*
* @return $this|Mage_Adminhtml_Block_Widget_Grid
*/
protected function _prepareCollection()
{
$product = $this->_getProduct();
$productId = $product->getId();
$collection = Mage::getResourceModel('sales/order_item_collection');
if (isset($productId) && !empty($productId)) {
$collection
->addFieldToFilter('product_id', $productId)
->getSelect()->joinInner(array(
'order' => $collection->getTable('sales/order')),
'order.entity_id = main_table.order_id',
array('increment_id', 'customer_firstname', 'customer_lastname', 'status', 'order_currency_code')
);
}
$this->setCollection($collection);
parent::_prepareCollection();
return $this;
}
/**
* Prepare the grid columns
*
* @return mixed $this|void
*
*/
protected function _prepareColumns()
{
$this->addColumn('real_order_id', array(
'header' => Mage::helper('demac_productorders')->__('Order #'),
'width' => '80px',
'type' => 'text',
'index' => 'increment_id',
));
if (!Mage::app()->isSingleStoreMode()) {
$this->addColumn('store_id', array(
'header' => Mage::helper('demac_productorders')->__('Purchased From (Store)'),
'index' => 'store_id',
'type' => 'store',
'store_view' => true,
'display_deleted' => true,
));
}
$this->addColumn('created_at', array(
'header' => Mage::helper('demac_productorders')->__('Purchased On'),
'index' => 'created_at',
'type' => 'datetime',
'width' => '100px',
));
$this->addColumn('customer_firstname', array(
'header' => Mage::helper('demac_productorders')->__('Customer First Name'),
'index' => 'customer_firstname',
));
$this->addColumn('customer_lastname', array(
'header' => Mage::helper('demac_productorders')->__('Customer Last Name'),
'index' => 'customer_lastname',
));
$this->addColumn('qty_ordered', array(
'header' => Mage::helper('demac_productorders')->__('Qty Ordered'),
'width' => '100px',
'type' => 'number',
'index' => 'qty_ordered',
));
$this->addColumn('row_total', array(
'header' => Mage::helper('demac_productorders')->__('Row Total'),
'index' => 'row_total',
'type' => 'currency',
'currency' => 'order_currency_code',
));
$this->addColumn('status', array(
'header' => Mage::helper('demac_productorders')->__('Status'),
'index' => 'status',
'type' => 'options',
'width' => '70px',
'options' => Mage::getSingleton('sales/order_config')->getStatuses(),
));
$this->addExportType('*/*/exportCsv', Mage::helper('demac_productorders')->__('CSV'));
return parent::_prepareColumns();
}
/**
* @return mixed|string
*/
public function getGridUrl()
{
return $this->_getData('grid_url')
? $this->_getData('grid_url')
: $this->getUrl('*/*/productordersGrid', array('_current' => true));
}
/**
* @param $item
*
* @return bool|string
*/
public function getRowUrl($item)
{
if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
$orderId = $item->getOrderId();
if (isset($orderId) && !empty($orderId)) {
return $this->getUrl('*/sales_order/view', array('order_id' => $orderId));
} else {
return false;
}
}
return false;
}
}

There are a few important functions here to note. _prepareCollection(), as you might correctly presume, prepares the grid collection object. Here we are joining a collection of order items with the orders table so that we may obtain the order’s increment ID, status, currency code, and customer information. All of which will be used in the display of the custom order grid. _prepareColumns() sets the columns and headings of that grid.

The getRowUrl() function sets the link for each row in the grid to send the end user to the order view page for the order in question.

In the __construct(), we are enabling the grid to support Ajax. Note the ID set here is consistent with the fallback URL specified in the getGridUrl() function of this same class.

Our module’s config.xml file specifies an adminhtml layout file at demac/productorders.xml. This layout sets the block to use during some of our Ajax actions. This is the same block class we just discussed.

<?xml version="1.0"?>
<layout version="0.1.0">
<adminhtml_catalog_product_productorders>
<block type="core/text_list" name="root" output="toHtml">
<block type="demac_productorders/adminhtml_catalog_product_edit_tab_orders"
name="catalog.product.edit.tab.orders"/>
</block>
</adminhtml_catalog_product_productorders>
<adminhtml_catalog_product_productordersgrid>
<block type="core/text_list" name="root" output="toHtml">
<block type="demac_productorders/adminhtml_catalog_product_edit_tab_orders"
name="catalog.product.edit.tab.orders"/>
</block>
</adminhtml_catalog_product_productordersgrid>
</layout>

Similarly, in our extension of the Adminhtml ProductController, our two methods productordersAction() and productordersGridAction() get the block from the layout we have just defined above. Our controller also includes an exportCsvAction() triggered on press of the button above the grid. It makes use of the getCsvFile() method from the Mage_Adminhtml_Block_Widget_Grid, which Demac_ProductOrders_Block_Adminhtml_Catalog_Product_Edit_Tab_Orders extends.

If we then view Aviator Sunglasses through the admin panel, we will see a new “Orders” tab on the left-hand menu. This tab will display our newly created order grid as follows:

Aviator Sunglasses _ Manage Products _ Catalog _ Magento Admin

Here, we can see that Linda Wood’s order is displayed with its quantity ordered and subtotal as described above. We can see, at a glance, the status of the order, the currency used, the date of purchase, the store it was purchased from, and the order number. Above, you will notice our CSV Export feature as well. In addition, if the row for Linda’s order is clicked, we will be directed to the full order information page as depicted earlier.

Once this is all in place, the order tab will appear and propagate for all products and orders both existing and new.

You can download the full Demac_ProductOrders module for yourself, to use or adapt, here.

For more Magento resources, download our free eBook containing 25 Real-World Examples of Magento Shopping Cart Price Rules





Download All 25 Examples!