More

    Custom Product Option and its use-case in Magento (Inchoo)

    This post is actually short tutorial on how to create custom option for product and how to extend Magento Sale Order grid to show custom option as “Delivery Date”. This is beginner guide tutorial so if you’re new to Magento you’ll find it useful. At the end it will be fully functional Magento Module.
    Imagine that you’ve gotten requirement that goes something like: “We would like to create custom option for each product in our Magento store, “Delivery Date“, so that our customers can set desired delivery date on every order. We are selling one product per order so setting custom option on each product would be enough for us. Further, our Operations Team needs additional information inside the Magento Sales grid. Requirement is to add a special sales grid within Magento back-end. New grid should be same as default Magento Sale grid with additional column “Delivery Date” that will be used by our Operations Team”.
    This requirement could be one of your next tasks, feel free to modify it or even use it as-is (whole module).
    If you’re not familiar with Magento’s product custom option please refer to next link.
    Lets start with clean Magento with sample data. Open product Ottoman (ID could be 51) in Magento back-end (administration): Catalog / Manage Products. Click on “Custom Option” tab. Now add “Delivery Date” custom option, similar as on image bellow:

    Now open product Ottoman under “Furniture” / “Living Room” in Magento front-end. You should see something like:

    And you’re all set. Simple as that – now you have for specified product custom product option that can be used by customers on your store.
    Now lets see how we can create grid for our custom option, similar to Magento’s Sales Order grid with our new data, Delivery Date. So lets start with creating our module.

    1. Tell Magento about our module
    2. Create configuration file for our module
    3. Create menu item in Magento back-end with ACL permissions
    4. Create controller file for back-end
    5. Create Helper file
    6. Create Block for back-end
      • Create initial Block
      • Create grid Block
      • Create Renderer file for grid Block
    7. Add blocks to Magento layout structure

    1. Tell Magento about our module

    Create file Inchoo_DateOrder.xml under app/etc/modules/ with the following content:

    <?xml version="1.0"?>
    <config>
        <modules>
            <Inchoo_DateOrder>
                <active>true</active>
                <codePool>local</codePool>
                <depends>
                    <Mage_Adminhtml />
                </depends>
            </Inchoo_DateOrder>
        </modules>
    </config>

    2. Create configuration file for our module

    Create file config.xml under app/code/local/Inchoo/DateOrder/etc/ with the following content:

    <?xml version="1.0"?>
    <config>
        <modules>
            <Inchoo_DateOrder>
                <version>1.0.0.0</version>
            </Inchoo_DateOrder>
        </modules>
        <global>
            <blocks>
                <operations_adminhtml>
                    <class>Inchoo_DateOrder_Block_Adminhtml</class>
                </operations_adminhtml>
            </blocks>
            <helpers>
                <operations>
                    <class>Inchoo_DateOrder_Helper</class>
                </operations>
            </helpers>
        </global>
        <admin>
            <routers>
                <adminhtml>
                    <args>
                        <modules>
                            <inchoo_dateorder after="Mage_Adminhtml">Inchoo_DateOrder_Adminhtml</inchoo_dateorder>
                        </modules>
                    </args>
                </adminhtml>
            </routers>
        </admin>
        <adminhtml>
    <!--         <translate> -->
    <!--             <modules> -->
    <!--                 <Inchoo_DateOrder> -->
    <!--                     <files> -->
    <!--                         <default>Inchoo_DateOrder.csv</default> -->
    <!--                     </files> -->
    <!--                 </Inchoo_DateOrder> -->
    <!--             </modules> -->
    <!--         </translate> -->
            <layout>
                <updates>
                    <operations>
                        <file>operations.xml</file>
                    </operations>
                </updates>
            </layout>
        </adminhtml>
    </config>

    Note that I’ve commented “translate” node under “adminhtml” XML node. If you have some sentences that needs to be translated on other languages feel free to un-comment those lines and add missing sentences.

    3. Create menu item in Magento back-end with ACL permissions

    Create file adminhtml.xml under app/code/local/Inchoo/DateOrder/etc/ with the following content:

    <?xml version="1.0"?>
    <config>
        <menu>
            <sales>
                <children>
                    <operations translate="title" module="operations">
                        <title>Operations</title>
                        <sort_order>1</sort_order>
                        <action>adminhtml/operations_order</action>
                    </operations>
                </children>
            </sales>
        </menu>
        <acl>
            <resources>
                <admin>
                    <children>
                        <sales>
                            <children>
                                <operations translate="title">
                                    <title>Operations</title>
                                    <sort_order>1</sort_order>
                                </operations>
                            </children>
                        </sales>
                    </children>
                </admin>
            </resources>
        </acl>
    </config>

    4. Create controller file for back-end

    Create file app/code/local/Inchoo/DateOrder/controllers/Adminhtml/Operations/OrderController.php with the following content:

    <?php
     
    /**
     * Adminhtml sales operations controller
     *
     * @category    Inchoo
     * @package     Inchoo_DateOrder
     */
    class Inchoo_DateOrder_Adminhtml_Operations_OrderController extends Mage_Adminhtml_Controller_Action
    {
     
        /**
         * Additional initialization
         *
         */
        protected function _construct()
        {
            $this->setUsedModuleName('Inchoo_DateOrder');
        }
     
        /**
         * Init layout, menu and breadcrumb
         *
         * @return Inchoo_DateOrder_Adminhtml_Operations_OrderController
         */
        protected function _initAction()
        {
            $this->loadLayout()
                ->_setActiveMenu('sales/order')
                ->_addBreadcrumb($this->__('Sales'), $this->__('Sales'))
                ->_addBreadcrumb($this->__('Operations'), $this->__('Operations'));
            return $this;
        }
     
        /**
         * Orders grid
         */
        public function indexAction()
        {
            $this->_title($this->__('Sales'))->_title($this->__('Operations'));
     
            $this->_initAction()
                ->renderLayout();
        }
     
        /**
         * Order grid
         */
        public function gridAction()
        {
            $this->loadLayout(false);
            $this->renderLayout();
        }
     
        /**
         * Export order grid to CSV format
         */
        public function exportCsvAction()
        {
            $fileName   = 'operations.csv';
            $grid       = $this->getLayout()->createBlock('operations_adminhtml/operations_order_grid');
            $this->_prepareDownloadResponse($fileName, $grid->getCsvFile());
        }
     
        /**
         *  Export order grid to Excel XML format
         */
        public function exportExcelAction()
        {
            $fileName   = 'operations.xml';
            $grid       = $this->getLayout()->createBlock('operations_adminhtml/operations_order_grid');
            $this->_prepareDownloadResponse($fileName, $grid->getExcelFile($fileName));
        }
     
    }

    5. Create Helper file

    Create file app/code/local/Inchoo/DateOrder/Helper/Data.php with the following content:

    <?php
     
    /**
     * Operations data helper
     *
     * @category   Inchoo
     * @package    Inchoo_DateOrder
     */
    class Inchoo_DateOrder_Helper_Data extends Mage_Core_Helper_Abstract
    {
     
    }

    6.1. Create initial Block

    Create file app/code/local/Inchoo/DateOrder/Block/Adminhtml/Operations/Order.php with the following content:

    <?php
     
    /**
     * Adminhtml operations orders block
     *
     * @category   Inchoo
     * @package    Inchoo_DateOrder
     */
    class Inchoo_DateOrder_Block_Adminhtml_Operations_Order extends Mage_Adminhtml_Block_Widget_Grid_Container
    {
     
        public function __construct()
        {
            $this->_controller = 'operations_order';
            $this->_blockGroup = 'operations_adminhtml';
            $this->_headerText = Mage::helper('operations')->__('Operations');
            $this->_addButtonLabel = Mage::helper('sales')->__('Create New Order');
            parent::__construct();
            if (!Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/create')) {
                $this->_removeButton('add');
            }
        }
     
    }

    6.2. Create grid Block

    Create file app/code/local/Inchoo/DateOrder/Block/Adminhtml/Operations/Order/Grid.php with the following content:

    <?php
     
    /**
     * Adminhtml operations orders grid
     *
     * @category   Inchoo
     * @package    Inchoo_DateOrder
     */
    class Inchoo_DateOrder_Block_Adminhtml_Operations_Order_Grid extends Mage_Adminhtml_Block_Widget_Grid
    {
        /**
         * Note that I could extend Mage_Adminhtml_Block_Sales_Order_Grid instead of Mage_Adminhtml_Block_Widget_Grid
         * and overwrite only methods __construct() & _prepareColumns()
         */
     
        public function __construct()
        {
            parent::__construct();
            $this->setId('operations_order_grid');
            $this->setUseAjax(true);
            $this->setDefaultSort('created_at');
            $this->setDefaultDir('DESC');
            $this->setSaveParametersInSession(true);
        }
     
        /**
         * Retrieve collection class
         *
         * @return string
         */
        protected function _getCollectionClass()
        {
            return 'sales/order_grid_collection';
        }
     
        protected function _prepareCollection()
        {
            $collection = Mage::getResourceModel($this->_getCollectionClass());
            $this->setCollection($collection);
            return parent::_prepareCollection();
        }
     
        protected function _prepareColumns()
        {
     
            $this->addColumn('real_order_id', array(
                'header'=> Mage::helper('sales')->__('Order #'),
                'width' => '80px',
                'type'  => 'text',
                'index' => 'increment_id',
            ));
     
            if (!Mage::app()->isSingleStoreMode()) {
                $this->addColumn('store_id', array(
                    'header'    => Mage::helper('sales')->__('Purchased From (Store)'),
                    'index'     => 'store_id',
                    'type'      => 'store',
                    'store_view'=> true,
                    'display_deleted' => true,
                ));
            }
     
            $this->addColumn('created_at', array(
                'header' => Mage::helper('sales')->__('Purchased On'),
                'index' => 'created_at',
                'type' => 'datetime',
                'width' => '100px',
            ));
     
            $this->addColumn('billing_name', array(
                'header' => Mage::helper('sales')->__('Bill to Name'),
                'index' => 'billing_name',
            ));
     
            $this->addColumn('shipping_name', array(
                'header' => Mage::helper('sales')->__('Ship to Name'),
                'index' => 'shipping_name',
            ));
     
            $this->addColumn('delivery_date', array(
                    'header' => Mage::helper('operations')->__('Delivery Date'),
                    'index' => 'entity_id',
                    'sortable'  => false,
                    'filter'    => false,
                    'renderer'  => 'operations_adminhtml/operations_order_renderer_delivery',
            ));
     
            $this->addColumn('base_grand_total', array(
                'header' => Mage::helper('sales')->__('G.T. (Base)'),
                'index' => 'base_grand_total',
                'type'  => 'currency',
                'currency' => 'base_currency_code',
            ));
     
            $this->addColumn('grand_total', array(
                'header' => Mage::helper('sales')->__('G.T. (Purchased)'),
                'index' => 'grand_total',
                'type'  => 'currency',
                'currency' => 'order_currency_code',
            ));
     
            $this->addColumn('status', array(
                'header' => Mage::helper('sales')->__('Status'),
                'index' => 'status',
                'type'  => 'options',
                'width' => '70px',
                'options' => Mage::getSingleton('sales/order_config')->getStatuses(),
            ));
     
            if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
                $this->addColumn('action',
                    array(
                        'header'    => Mage::helper('sales')->__('Action'),
                        'width'     => '50px',
                        'type'      => 'action',
                        'getter'     => 'getId',
                        'actions'   => array(
                            array(
                                'caption' => Mage::helper('sales')->__('View'),
                                'url'     => array('base'=>'*/sales_order/view'),
                                'field'   => 'order_id'
                            )
                        ),
                        'filter'    => false,
                        'sortable'  => false,
                        'index'     => 'stores',
                        'is_system' => true,
                ));
            }
            $this->addRssList('rss/order/new', Mage::helper('sales')->__('New Order RSS'));
     
            $this->addExportType('*/*/exportCsv', Mage::helper('sales')->__('CSV'));
            $this->addExportType('*/*/exportExcel', Mage::helper('sales')->__('Excel XML'));
     
            return parent::_prepareColumns();
        }
     
        protected function _prepareMassaction()
        {
            $this->setMassactionIdField('entity_id');
            $this->getMassactionBlock()->setFormFieldName('order_ids');
            $this->getMassactionBlock()->setUseSelectAll(false);
     
            if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/cancel')) {
                $this->getMassactionBlock()->addItem('cancel_order', array(
                     'label'=> Mage::helper('sales')->__('Cancel'),
                     'url'  => $this->getUrl('*/sales_order/massCancel'),
                ));
            }
     
            if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/hold')) {
                $this->getMassactionBlock()->addItem('hold_order', array(
                     'label'=> Mage::helper('sales')->__('Hold'),
                     'url'  => $this->getUrl('*/sales_order/massHold'),
                ));
            }
     
            if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/unhold')) {
                $this->getMassactionBlock()->addItem('unhold_order', array(
                     'label'=> Mage::helper('sales')->__('Unhold'),
                     'url'  => $this->getUrl('*/sales_order/massUnhold'),
                ));
            }
     
            $this->getMassactionBlock()->addItem('pdfinvoices_order', array(
                 'label'=> Mage::helper('sales')->__('Print Invoices'),
                 'url'  => $this->getUrl('*/sales_order/pdfinvoices'),
            ));
     
            $this->getMassactionBlock()->addItem('pdfshipments_order', array(
                 'label'=> Mage::helper('sales')->__('Print Packingslips'),
                 'url'  => $this->getUrl('*/sales_order/pdfshipments'),
            ));
     
            $this->getMassactionBlock()->addItem('pdfcreditmemos_order', array(
                 'label'=> Mage::helper('sales')->__('Print Credit Memos'),
                 'url'  => $this->getUrl('*/sales_order/pdfcreditmemos'),
            ));
     
            $this->getMassactionBlock()->addItem('pdfdocs_order', array(
                 'label'=> Mage::helper('sales')->__('Print All'),
                 'url'  => $this->getUrl('*/sales_order/pdfdocs'),
            ));
     
            $this->getMassactionBlock()->addItem('print_shipping_label', array(
                 'label'=> Mage::helper('sales')->__('Print Shipping Labels'),
                 'url'  => $this->getUrl('*/sales_order_shipment/massPrintShippingLabel'),
            ));
     
            return $this;
        }
     
        public function getRowUrl($row)
        {
            if (Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/view')) {
                return $this->getUrl('*/sales_order/view', array('order_id' => $row->getId()));
            }
            return false;
        }
     
        public function getGridUrl()
        {
            return $this->getUrl('*/*/grid', array('_current'=>true));
        }
     
    }

    6.3. Create Renderer file for grid Block

    Create file app/code/local/Inchoo/DateOrder/Block/Adminhtml/Operations/Order/Renderer/Delivery.phpwith the following content:

    <?php
     
    /**
     * Adminhtml operations orders grid renderer
     *
     * @category   Inchoo
     * @package    Inchoo_DateOrder
     */
    class Inchoo_DateOrder_Block_Adminhtml_Operations_Order_Renderer_Delivery
        extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
    {
        public function render(Varien_Object $row)
        {
            //load first item of the order
            $orderItem = Mage::getResourceModel('sales/order_item_collection')
                            ->addFieldToFilter('order_id', $row->getId())
                            ->getFirstItem()
                            ;
     
            $orderItemOptions = $orderItem->getProductOptions();
     
            //if product doesn't have options stop with rendering
            if (!array_key_exists('options', $orderItemOptions)) {
                return;
            }
     
            $orderItemOptions = $orderItemOptions['options'];
     
            //if product options isn't array stop with rendering
            if (!is_array($orderItemOptions)) {
                return;
            }
     
            foreach ( $orderItemOptions as $orderItemOption) {
     
                if ($orderItemOption['label'] === 'Delivery Date') {
                    if (array_key_exists('value', $orderItemOption)) {
                        return $orderItemOption['value'];
                    }
                }
     
            }
     
            //if product options doesn't have Delivery Date custom option return void
            return;
        }
    }

    7. Add blocks to Magento layout structure

    Create file app/design/adminhtml/default/default/layout/operations.xml with the following content:

    <?xml version="1.0"?>
    <layout>
        <adminhtml_operations_order_grid>
            <update handle="formkey"/>
            <block type="operations_adminhtml/operations_order_grid" name="operations_order.grid" output="toHtml"></block>
        </adminhtml_operations_order_grid>
     
        <adminhtml_operations_order_index>
            <reference name="content">
                <block type="operations_adminhtml/operations_order" name="operations_order.grid.container"></block>
            </reference>
        </adminhtml_operations_order_index>
    </layout>

    Now all you need to do is to add Operations menu item to your administrator role (sign out/sign in – if needed) and you can use your new grid in Magento back-end.
    Note that when customers add some custom options for product, data will be saved in tablesales_flat_order_item, in column product_options. Additionally, as data in table sales_flat_order_item is serialized its a bit complicated for this tutorial to show you how you can filter & sort by “Delivery Date” and because of that sorting and filtering by it has been disabled.
    For the end, this is how our grid looks like in Magento back-end:

     
     

    Recent Articles

    spot_img

    Related Stories

    Stay on op - Ge the daily news in your inbox