How to move fieldset addfield in magento 1.9 to top

Because I haven't found any detailed article on how to create a report and how it works, I decided to write one myself, and try to give you some details, not just a plain-code-figure-out-everything-yourself stuff. The example would be quite simple, but it just fits for an excersice: list the orders grand total and shipping amount, and - to give this story a little twist - we would like to display how much percent was the shipping amount of the order's total. We would like to display totals too under our grid. Acceptance criteria for our module:

  • Ability to filter in a given date interval
  • Ability to change date interval (days, months or years)
  • Ability to filter results with a non-zero shipping percent only
  • Ability to export to CSV and MS Excel In the example code I would like to use some of the best practices, and to follow the conventions as much as possible. I created a public git repository from where you could download the whole source code. If you are impatient, scroll to the end of this article for the link.

About the Reports in a Nutshell

Basically it consists a grid, a collection and a form, where the form has fields to filter the displayed results of the grid. The grid displays the collection's items using the applied filters. There is an enermous amount of entry points which we could use to change data during runtime, but we won't use many of them.

Creating the base module

We will create some blocks, models, helpers for our module, overload a controller, define a layout, then place the whole thing into the admin menu. We have to define models because we will use a collection, it should have blocks to display the grid and the form, while the helper will handle the translations and it's required because we make an admin module. We will place our files under the 'local' codepool, under the

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

1 vendor and the module's name would be

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

2. You can notice some difference while creating a module in the admin area compared to a frontend one. We will overload the controller under the config's 'admin' node instead of adding a new frontname to the system, also applying the layout updates would be in a different node named

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

3. You may wonder why we won't place it under the same node as the controller; this could be traced back to legacy reasons. This node is also the place for the admin menu configuration, but we separate it into a file named after this node (adminhtml.xml). This is a feature of Magento, you could separate your module's configuration by the node names used. Usually we do this with system.xml, adminhtml.xml and api.xml/api_v2.xml, depending on needs.

Configuration files

First of all, we will write our module xml. Because we'll work in the

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

4 codepool, we should place all of our files under the

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

5 directory.

app/etc/modules/My_Reports.xml

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <active>true</active>
            <version>0.1.0</version>
            <codePool>local</codePool>
        </My_Reports>
    </modules>
</config>

In

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

6, we tell Magento's admin router to search the controller first in our module, before

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

7, then add the layout update file for creating the report's user interface.

app/code/local/My/Reports/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

After that, add our module to the admin menu under Report > Sales. We define some basic ACL rule too, which allows every user to operate with our grid.

app/code/local/My/Reports/etc/adminhtml.xml

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

To get a working admin module, we should create a helper class. Since we haven't got any logic which we should share between blocks, controllers or models, we just inherit everything from

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

8 and leave the body empty. There is a convention to use the helper's translate method to hook translations through it, so let's follow it on our code!

app/code/local/My/Reports/Helper/Data.php

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

Controller

The controller's

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

9 and

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

0 methods could be familiar from the

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

1. In the

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

2 we will use these methods to pass values from the request object to the filter. The methods starting with 'export' shall export our data to the appropriate formats. Luckily we don't have to code the exportation logic ourself, it's already implemented by the Magento Team (at least one thing less to do). Because the export is a part of the grid, we have the opportunity to export anything what the grid could display.

app/code/local/My/Reports/controllers/Adminhtml/My/ReportsController.php

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

Layout, Grid Container

The

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

2 supplies our blocks with data, therefore it's time to start creating them! Let's start right now with the

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

4. As you can see, we will need a container block, which would be the place of the grid and the filter form. Notice that nothing describes the grid block here. Don't worry, the container should add it later, dynamically.

app/design/adminhtml/default/default/layout/my_reports.xml

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

Let's go on with the container. This block should build the the grid block in it's parent class'

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

5 method in the following way:

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

6. The {blockGroup} is the block alias (

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

7), which we already defined in our

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

6 under the blocks node, and the {controller} is this block's identifier (

<?xml version="1.0"?>
<config>
    <menu>
        <report>
            <children>
                <salesroot translate="title">
                    <children>
                        <my_reports translate="title">
                            <title>My Custom Reports</title>
                            <action>adminhtml/my_reports</action>
                            <sort_order>100</sort_order>
                        </my_reports>
                    </children>
                </salesroot>
            </children>
        </report>
    </menu>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <my_reports>
                                        <title>My Reports Section</title>
                                    </my_reports>
                                </children>
                            </config>
                        </children>
                    </system>
                    <report>
                        <children>
                            <salesroot>
                                <children>
                                    <my_reports translate="title">
                                        <title>My Custom Reports</title>
                                        <children>
                                            <view translate="title">
                                                <title>View</title>
                                            </view>
                                        </children>
                                    </my_reports>
                                </children>
                            </salesroot>
                        </children>
                    </report>
                </children>
            </admin>
        </resources>
    </acl>
</config>

9). The grid block's name would be

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

0 then.

app/code/local/My/Reports/Block/Adminhtml/Report.php

<?php
class My_Reports_Block_Adminhtml_Report
    extends Mage_Adminhtml_Block_Widget_Grid_Container
{
    /**
  • This is your module alias / protected $_blockGroup = 'my_reports'; /*
  • This is the controller's name (this block) / protected $_controller = 'adminhtml_report'; / Note: the grid block's name would prepare from $_blockGroup and $_controller with the suffix '_grid'. So the complete block would called my_reports/adminhtml_report_grid . As you already guessed it, this will resolve to the class My_Reports_Adminhtml_Report_Grid . / /*
  • Prepare grid container, add and remove additional buttons / public function construct() { // The head title of the grid $this->_headerText = Mage::helper('my_reports')->('My Custom Reports'); // Set hard-coded template. As you can see, the layout.xml // attribute is ineffective, but we keep up with conventions $this->setTemplate('report/grid/container.phtml'); // call parent constructor and let it add the buttons parent::construct(); // we create a report, not just a standard grid, so remove add button, we don't need it this time $this->_removeButton('add'); // add a button to our form to let the user kick-off our logic from the admin $this->addButton('filter_form_submit', array( 'label' => Mage::helper('my_reports')->('Show Report'), 'onclick' => 'filterFormSubmit()' )); } /*
  • This function will prepare our filter URL
  • @return string / public function getFilterUrl() { $this->getRequest()->setParam('filter', null); return $this->getUrl('/*/index', array('_current' => true)); } }

Grid

The grid connects our backend data and the logic in templates to display everything on the frontend, so it's a bit of both worlds. The original sales report grid contains an abstract and a concrete class implementation, but for the purpose of easy understanding, we will place everything into only one class. The code which deals with displaying data on the user interface should be prepared in the

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

1. Using the

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

2 key you can choose one column renderer from the bundled ones (you could find the full list of the renderers at

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

3). However, there isn't one which could handle the percent values, therefore we should create one by ourselves. The

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

4 would attach the SQL result's column to the column renderer (you should define the 'alias' here as you defined it in your query in the resource model, for example you could see how we specified the

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

5 column). The method which deals with supplying data from the backend is

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

6. Here we pass the values from the filters to the collection within the

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

7 method.

app/code/local/My/Reports/Block/Adminhtml/Report/Grid.php

<?php
class My_Reports_Block_Adminhtml_Report_Grid
    extends Mage_Adminhtml_Block_Widget_Grid
{
    // add vars used by our methods
    /**
  • Grouped class name of used collection by this grid
  • @var string / protected $_resourceCollectionName = 'my_reports/report_collection'; /*
  • List of columns to aggregate by
  • @var array / protected $_aggregatedColumns; /*
  • Basic setup of our grid / public function construct() { parent::__construct(); // change behaviour of grid. This time we won't use pager and ajax functions $this->setPagerVisibility(false); $this->setUseAjax(false); $this->setFilterVisibility(false); // set message for empty result $this->setEmptyCellLabel(Mage::helper('my_reports')->('No records found.')); // set grid ID in adminhtml $this->setId('myReportsGrid'); // set our grid to obtain totals $this->setCountTotals(true); } // add getters /*
  • Returns the resource collection name which we'll apply filters and display results
  • @return string / public function getResourceCollectionName() { return $this->_resourceCollectionName; } /*
  • Factory method for our resource collection
  • @return Mage_Core_Model_Mysql4_Collection_Abstract / public function getResourceCollection() { $resourceCollection = Mage::getResourceModel($this->getResourceCollectionName()); return $resourceCollection; } /*
  • Gets the actual used currency code.
  • We will convert every currency value to this currency.
  • @return string / public function getCurrentCurrencyCode() { return Mage::app()->getStore()->getBaseCurrencyCode(); } /*
  • Get currency rate, base to given currency
  • @param string|Mage_Directory_Model_Currency $toCurrency currency code
  • @return int / public function getRate($toCurrency) { return Mage::app()->getStore()->getBaseCurrency()->getRate($toCurrency); } /*
  • Return totals data
  • Count totals if it's not previously counted and set to retrieve
  • @return Varien_Object / public function getTotals() { $result = parent::getTotals(); if (!$result && $this->getCountTotals()) { $filterData = $this->getFilterData(); $totalsCollection = $this->getResourceCollection(); // apply our custom filters on collection $this->_addCustomFilter( $totalsCollection, $filterData ); // isTotals is a flag, we will deal with this in the resource collection $totalsCollection->isTotals(true); // set totals row even if we didn't got a result if ($totalsCollection->count() < 1) { $this->setTotals(new Varien_Object); } else { $this->setTotals($totalsCollection->getFirstItem()); } $result = parent::getTotals(); } return $result; } // prepare columns and collection /*
  • Prepare our grid's columns to display
  • @return My_Reports_Block_Adminhtml_Grid / protected function prepareColumns() { // get currency code and currency rate for the currency renderers. // our orders could be in different currencies, therefore we should convert the values to the base currency $currencyCode = $this->getCurrentCurrencyCode(); $rate = $this->getRate($currencyCode); // add our first column, period which represents a date $this->addColumn('period', array( 'header' => Mage::helper('my_reports')->_('Period'), 'index' => 'created_at', // 'index' attaches a column from the SQL result set to the grid 'renderer' => 'adminhtml/report_sales_grid_column_renderer_date', 'width' => 100, 'sortable' => false, 'period_type' => $this->getFilterData()->getPeriodType() // could be day, month or year )); // add base grand total w/ a currency renderer, and add totals $this->addColumn('base_grand_total', array( 'header' => Mage::helper('my_reports')->('Grand Total'), 'index' => 'base_grand_total', // type defines a grid column renderer; you could find the complete list // and the exact aliases at Mage_Adminhtml_Block_Widget_Grid_Column::_getRendererByType() 'type' => 'currency', 'currency_code' => $currencyCode, // set currency code.. 'rate' => $rate, // and currency rate, used by the column renderer 'total' => 'sum' )); // add the next column shipping_amount, with an average on totals $this->addColumn('base_shipping_amount', array( 'header' => Mage::helper('my_reports')->('Shipping Amount'), 'index' => 'base_shipping_amount', 'type' => 'currency', 'currency_code' => $currencyCode, 'rate' => $rate, 'total' => 'sum' )); // rate, where base_shipping_amount/base_grand_total is a percent $this->addColumn('shipping_rate', array( 'header' => Mage::helper('my_reports')->('Shipping Rate'), 'index' => 'shipping_rate', 'renderer' => 'my_reports/adminhtml_report_grid_column_renderer_percent', 'decimals' => 2, 'total' => 'avg' )); // add export types $this->addExportType('//exportCsv', Mage::helper('my_reports')->('CSV')); $this->addExportType('//exportExcel', Mage::helper('my_reports')->('MS Excel XML')); return parent::_prepareColumns(); } /*
  • Prepare our collection which we'll display in the grid
  • First, get the resource collection we're dealing with, with our custom filters applied.
  • In case of an export, we're done, otherwise calculate the totals
  • @return My_Reports_Block_Adminhtml_Grid / protected function _prepareCollection() { $filterData = $this->getFilterData(); $resourceCollection = $this->getResourceCollection(); // get our resource collection and apply our filters on it $this->_addCustomFilter( $resourceCollection, $filterData ); // attach the prepared collection to our grid $this->setCollection($resourceCollection); // skip totals if we do an export (calling getTotals would be a duplicate, because // the export method calls it explicitly) if ($this->_isExport) { return $this; } // count totals if needed if ($this->getCountTotals()) { $this->getTotals(); } return parent::_prepareCollection(); } /*
  • Apply our custom filters on collection
  • @param Mage_Core_Model_Mysql4_Collection_Abstract $collection
  • @param Varien_Object $filterData
  • @return My_Reports_Block_Adminhtml_Report_Grid / protected function _addCustomFilter($collection, $filterData) { $collection ->setPeriodType($filterData->getPeriodType()) ->setDateRange($filterData->getFrom(), $filterData->getTo()) ->isShippingRateNonZeroOnly($filterData->getShippingRate() ? true : false) ->setAggregatedColumns($this->_getAggregatedColumns()); return $this; } /*
  • Returns the columns we specified to summarize totals
  • Collect all columns we added totals to.
  • The returned array would be ie. 'base_grand_total' => 'sum'
  • @return array */ protected function _getAggregatedColumns() { if (!isset($this->_aggregatedColumns) && $this->getColumns()) { $this->_aggregatedColumns = array(); foreach ($this->getColumns() as $column) { if ($column->hasTotal()) { $this->_aggregatedColumns[$column->getId()] = $column->getTotal(); } } } return $this->_aggregatedColumns; } }

We don't have a renderer to display the percent values yet, so we have to create it. Because every column object inherits from

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

8, you could pass any value to your column renderer in the grid's

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

9 method. We will create our renderer by using this capability, but because we should have default values, we should wrap the getters within our own methods. If you'd like to display the value differently in an export, you have to overwrite the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

0 method (by default it returns with the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

1 method's result). Also, it's worth mentioning that there are two column block types, the one which we would like to create now, and an other one which deals with inline filtering on values, placed on the top of the grid (we turned it off this time, see

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

2 in the grid class). If you are interested, you could find everything in

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

3.

app/code/local/My/Reports/Block/Adminhtml/Report/Grid/Column/Renderer/Percent.php

<?php
class My_Reports_Block_Adminhtml_Report_Column_Renderer_Percent
    extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
{
    // default (fallback) values, if not specified from outside
    /**
  • Default value for rounding value by
  • @var int / const DECIMALS = 2; // render the field /*
  • Renders grid column
  • @param Varien_Object $row
  • @return string / public function render(Varien_Object $row) { $value = $this->_getValue($row); $decimals = $this->_getDecimals(); return number_format($value, $decimals) . '%'; } // add getter for decimals /*
  • Get decimal to round value by
  • The decimals value could be changed with specifying it from outside using
  • a setter method supported by Varien_Object (ie. with setData('decimals', 2) or setDecimals(2))
  • @return int */ protected function _getDecimals() { $decimals = $this->getDecimals(); // this is a magic getter return !is_null($decimals) ? $decimals : self::DECIMALS; } }

Form

We are already done with almost everything in our layout, except the filter form. This is a block which wraps the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

4 with a template (

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

5). We will create a fieldset and place our form elements in it, and put the options for the select elements to protected getters. We may have to modify the fields in runtime from outside the class, therefore we will add functionality to achieve this behaviour.

app/code/local/My/Reports/Block/Adminhtml/Filter/Form.php

<?php
class My_Reports_Block_Adminhtml_Filter_Form
    extends Mage_Adminhtml_Block_Widget_Form
{
    /**
  • This will contain our form element's visibility
  • @var array / protected $_fieldVisibility = array(); /*
  • Field options
  • @var array / protected $_fieldOptions = array(); /*
  • Sets a form element to be visible or not
  • @param string $fieldId
  • @param bool $visibility
  • @return My_Reports_Block_Adminhtml_Filter_Form / public function setFieldVisibility($fieldId, $visibility) { $this->_fieldVisibility[$fieldId] = $visibility ? true : false; return $this; } /*
  • Returns the field is visible or not. If we hadn't set a value
  • for the field previously, it will return the value defined in the
  • defaultVisibility parameter (it's true by default)
  • @param string $fieldId
  • @param bool $defaultVisibility
  • @return bool / public function getFieldVisibility($fieldId, $defaultVisibility = true) { if (isset($this->_fieldVisibility[$fieldId])) { return $this->_fieldVisibility[$fieldId]; } return $defaultVisibility; } /*
  • Set field option(s)
  • @param string $fieldId
  • @param string|array $option if option is an array, loop through it's keys and values
  • @param mixed $value if option is an array this option is meaningless
  • @return My_Reports_Block_Adminhtml_Filter_Form / public function setFieldOption($fieldId, $option, $value = null) { if (is_array($option)) { $options = $option; } else { $options = array($option => $value); } if (!isset($this->_fieldOptions[$fieldId])) { $this->_fieldOptions[$fieldId] = array(); } foreach ($options as $key => $value) { $this->_fieldOptions[$fieldId][$key] = $value; } return $this; } /*
  • Prepare our form elements
  • @return My_Reports_Block_Adminhtml_Filter_Form / protected function prepareForm() { // inicialise our form $actionUrl = $this->getCurrentUrl(); $form = new Varien_Data_Form(array( 'id' => 'filter_form', 'action' => $actionUrl, 'method' => 'get' )); // set ID prefix for all elements in our form $htmlIdPrefix = 'my_reports'; $form->setHtmlIdPrefix($htmlIdPrefix); // create a fieldset to add elements to $fieldset = $form->addFieldset( 'base_fieldset', array( 'legend' => Mage::helper('my_reports')->('Filter') ) ); // prepare our filter fields and add each to the fieldset // date filter $dateFormatIso = Mage::app() ->getLocale() ->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); $fieldset->addField('from', 'date', array( 'name' => 'from', 'format' => $dateFormatIso, 'image' => $this->getSkinUrl('images/grid-cal.gif'), 'label' => Mage::helper('my_reports')->('From'), 'title' => Mage::helper('my_reports')->('From') )); $fieldset->addField('to', 'date', array( 'name' => 'to', 'format' => $dateFormatIso, 'image' => $this->getSkinUrl('images/grid-cal.gif'), 'label' => Mage::helper('my_reports')->('To'), 'title' => Mage::helper('my_reports')->('To') )); $fieldset->addField('period_type', 'select', array( 'name' => 'period_type', 'options' => $this->_getPeriodTypeOptions(), 'label' => Mage::helper('my_reports')->('Period') )); // non-zero shipping rate filter $fieldset->addField('shipping_rate', 'select', array( 'name' => 'shipping_rate', 'options' => $this->getShippingRateSelectOptions(), 'label' => Mage::helper('my_reports')->_('Show values where shipping rate greater than 0') )); $form->setUseContainer(true); $this->setForm($form); return $this; } /*
  • Get period type options
  • @return array / protected function getPeriodTypeOptions() { $options = array( 'day' => Mage::helper('my_reports')->_('Day'), 'month' => Mage::helper('my_reports')->('Month'), 'year' => Mage::helper('my_reports')->('Year'), ); return $options; } /*
  • Returns options for shipping rate select
  • @return array / protected function _getShippingRateSelectOptions() { $options = array( '0' => 'Any', '1' => 'Specified' ); return $options; } /*
  • Inicialise form values
  • Called after prepareForm, we apply the previously set values from filter on the form
  • @return My_Reports_Block_Adminhtml_Filter_Form / protected function _initFormValues() { $filterData = $this->getFilterData(); $this->getForm()->addValues($filterData->getData()); return parent::_initFormValues(); } /*
  • Apply field visibility and field options on our form fields before rendering
  • @return My_Reports_Block_Adminhtml_Filter_Form / protected function _beforeHtml() { $result = parent::_beforeHtml(); $elements = $this->getForm()->getElements(); // iterate on our elements and select fieldsets foreach ($elements as $element) { $this->_applyFieldVisibiltyAndOptions($element); } return $result; } /*
  • Apply field visibility and options on fieldset element
  • Recursive
  • @param Varien_Data_Form_Element_Fieldset $element
  • @return Varien_Data_Form_Element_Fieldset */ protected function _applyFieldVisibiltyAndOptions($element) { if ($element instanceof Varien_Data_Form_Element_Fieldset) { foreach ($element->getElements() as $fieldElement) { // apply recursively if ($fieldElement instanceof Varien_Data_Form_Element_Fieldset) { $this->_applyFieldVisibiltyAndOptions($fieldElement); continue; } $fieldId = $fieldElement->getId(); // apply field visibility if (!$this->getFieldVisibility($fieldId)) { $element->removeField($fieldId); continue; } // apply field options if (isset($this->_fieldOptions[$fieldId])) { $fieldOptions = $this->_fieldOptions[$fieldId]; foreach ($fieldOptions as $k => $v) { $fieldElement->setDataUsingMethod($k, $v); } } } } return $element; } }

Collection

Finally arrived to the point when we will code our last class: the collection. It will collect our data which we would like to display in the grid rows. We should have to write some getters, those ones which we already referenced to in the

<?php
/**
  • Default helper for our Admin module
  • Hook for translations */ class My_Reports_Helper_Data extends Mage_Core_Helper_Abstract { }

7 method. The SQL query building starts in the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

7 method. It is originally called from the parent class' constructor, but it isn't fit for us this case, because the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

8 flag is set after the object has been instantiated, we will move the select initialisation into the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

9 method. We should define the displayed columns in the

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

0 method based on the

<?php
class My_Reports_Adminhtml_My_ReportsController
    extends Mage_Adminhtml_Controller_Action
{
    /**
  • Initialize titles and navigation breadcrumbs
  • @return My_Reports_Adminhtml_ReportsController / protected function initAction() { $this->title($this->('Reports')) ->title($this->_('Sales')) ->title($this->_('My Custom Reports')); $this->loadLayout() ->setActiveMenu('report/sales') ->addBreadcrumb(Mage::helper('my_reports')->('Reports'), Mage::helper('my_reports')->('Reports')) ->_addBreadcrumb(Mage::helper('my_reports')->('Sales'), Mage::helper('my_reports')->('Sales')) ->_addBreadcrumb(Mage::helper('my_reports')->('My Custom Reports'), Mage::helper('my_reports')->('My Custom Reports')); return $this; } /*
  • Prepare blocks with request data from our filter form
  • @return My_Reports_Adminhtml_ReportsController / protected function _initReportAction($blocks) { if (!is_array($blocks)) { $blocks = array($blocks); } $requestData = Mage::helper('adminhtml')->prepareFilterString($this->getRequest()->getParam('filter')); $requestData = $this->_filterDates($requestData, array('from', 'to')); $params = $this->_getDefaultFilterData(); foreach ($requestData as $key => $value) { if (!empty($value)) { $params->setData($key, $value); } } foreach ($blocks as $block) { if ($block) { $block->setFilterData($params); } } return $this; } /*
  • Grid action / public function indexAction() { $this->_initAction(); $gridBlock = $this->getLayout()->getBlock('adminhtml_report.grid'); $filterFormBlock = $this->getLayout()->getBlock('grid.filter.form'); $this->_initReportAction(array( $gridBlock, $filterFormBlock )); $this->renderLayout(); } /*
  • Export reports to CSV file / public function exportCsvAction() { $fileName = 'my_reports.csv'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getCsvFile()); } /*
  • Export reports to Excel XML file / public function exportExcelAction() { $fileName = 'my_reports.xml'; $grid = $this->getLayout()->createBlock('my_reports/adminhtml_report_grid'); $this->_initReportAction($grid); $this->_prepareDownloadResponse($fileName, $grid->getExcelFile()); } /*
  • Returns default filter data
  • @return Varien_Object */ protected function _getDefaultFilterData() { return new Varien_Object(array( 'from' => date('Y-m-d G:i:s', strtotime('-1 month -1 day')), 'to' => date('Y-m-d G:i:s', strtotime('-1 day')) )); } }

8 flag's value. The

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

2 method builds the SQL query's columns part in totals mode. In the original Sales Report the aggregated columns are prepared in the grid in this format:

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

3, but I think building queries are the resource model's responsibility; therefore I chose a different realisation (take a look at the

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

4 method). If you'd like to debug and see the actual queries, overwrite the

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

5 method. The method's two parameters explains the functionality behind them. For a little hint you could take a look at

<?xml version="1.0"?>
<layout version="0.1.0">
    <adminhtml_my_reports_index>
        <reference name="content">
            <block type="my_reports/adminhtml_report" template="report/grid/container.phtml" name="my_reports_report_grid_container">
                <block type="my_reports/adminhtml_filter_form" name="grid.filter.form" />
            </block>
        </reference>
    </adminhtml_my_reports_index>
</layout>

6.

app/code/local/My/Reports/Model/Mysql4/Report/Collection.php

<?xml version="1.0"?>
<config>
    <modules>
        <My_Reports>
            <version>0.1.0</version>
            <depends>
                <Mage_Adminhtml />
                <Mage_Sales />
            </depends>
        </My_Reports>
    </modules>
    <global>
        <models>
            <my_reports>
                <class>My_Reports_Model</class>
                <resourceModel>my_reports_mysql4</resourceModel>
            </my_reports>
            <my_reports_mysql4>
                <class>My_Reports_Model_Mysql4</class>
            </my_reports_mysql4>
        </models>
        <helpers>
            <my_reports>
                <class>My_Reports_Helper</class>
            </my_reports>
        </helpers>
        <blocks>
            <my_reports>
                <class>My_Reports_Block</class>
            </my_reports>
        </blocks>
    </global>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <My_Reports before="Mage_Adminhtml">My_Reports_Adminhtml</My_Reports>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <adminhtml>
        <layout>
            <updates>
                <my_reports>
                    <file>my_reports.xml</file>
                </my_reports>
            </updates>
        </layout>
    </adminhtml>
</config>

0

Final words

As you could see, it's not rocket science to create a report. However it could be scary at first, but I hope I could give you a better understanding of the process. Send me a beer if I was able to help you :) Comments and opinions are more than welcome. The module is available on GitHub: https://github.com/technodelight/magento_custom_reports_example