Programmatically add new order state and status in Magento

Magento Orders Menu ItemRecently my task was to allow client to have different "type" of order in Magento. Requirement was that these orders need to have their own grid in Magento admin, and also that they need to be processed somewhat differently than regular orders. I have solved this task by adding new order states and statuses to be used only for this type of orders. To support this design it was also required to create new payment method so that orders of this type can be initiated into new order state automatically after being placed using this payment method. In this article I'm bringing you some code snippets and notes on how to implement this kind of solution.

Introduction

Before getting into the code, if feel the need to explain order status - state concept of Magento. General idea is that you have order states and order statuses that belong to specific order states. In my opinion two "entities" were introduced to increase granularity trough out the life cycle of an order.

First a few words about order statuses as something user has under control from Magento admin (System -> Order Statuses). Order statuses are kept inside sales_order_status table whose structure and default contents is displayed on the following listings.

Table sales_order_status structure
mysql> DESCRIBE sales_order_status;
+--------+--------------+------+-----+---------+-------+
| Field  | Type         | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| status | varchar(32)  | NO   | PRI | NULL    |       |
| label  | varchar(128) | NO   |     | NULL    |       |
+--------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
Table sales_order_status data
mysql> SELECT * FROM sales_order_status;
+--------------------------+--------------------------+
| status                   | label                    |
+--------------------------+--------------------------+
| canceled                 | Canceled                 |
| closed                   | Closed                   |
| complete                 | Complete                 |
| fraud                    | Suspected Fraud          |
| holded                   | On Hold                  |
| payment_review           | Payment Review           |
| paypal_canceled_reversal | PayPal Canceled Reversal |
| paypal_reversed          | PayPal Reversed          |
| pending                  | Pending                  |
| pending_payment          | Pending Payment          |
| pending_paypal           | Pending PayPal           |
| processing               | Processing               |
+--------------------------+--------------------------+
12 rows in set (0.00 sec)

Next there are order states, something user can not change from Magento admin. Order states as well as mappings of orders statuses to states are kept inside sales_order_status_state table. For structure and data please refer to following listings.

Table sales_order_status_state structure
mysql> DESCRIBE sales_order_status_state;
+------------+----------------------+------+-----+---------+-------+
| Field      | Type                 | Null | Key | Default | Extra |
+------------+----------------------+------+-----+---------+-------+
| status     | varchar(32)          | NO   | PRI | NULL    |       |
| state      | varchar(32)          | NO   | PRI | NULL    |       |
| is_default | smallint(5) unsigned | NO   |     | NULL    |       |
+------------+----------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
Table sales_order_status_state data
mysql> SELECT * FROM sales_order_status_state;
+-----------------+-----------------+------------+
| status          | state           | is_default |
+-----------------+-----------------+------------+
| canceled        | canceled        |          1 |
| closed          | closed          |          1 |
| complete        | complete        |          1 |
| fraud           | payment_review  |          0 |
| holded          | holded          |          1 |
| payment_review  | payment_review  |          1 |
| pending         | new             |          1 |
| pending_payment | pending_payment |          1 |
| processing      | processing      |          1 |
+-----------------+-----------------+------------+
9 rows in set (0.00 sec)

Important thing to point out is that information that ends up being attached to order (sales_flat_order table) is order status information. Because of that we can freely say that order states are used simply to logically organize order statuses.

The code

First piece of code is one that isn't actually related to order states and statuses, but it is required to allow us to have another "type" of order as described at the beginning of this article. I'm referring to Magento installer script for adding new order attribute to order EAV entity and new column to it's close cousin sales_flat_order_grid table. Latter one contains subset of order data and it's purpose is to increase performance on Orders grid in Magento admin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$installer = $this;
$installer->startSetup();
 
$installer->addAttribute(
    'order',
    'is_specialorder',
    array(
        'type' => 'int',
        'default' => 0,
        'grid' => true,
        'unsigned'  => true,
    )
);
 
$installer->endSetup();

The grid parameter is what instructs Magento to keep this new attribute and it's clone from sales_flat_order_grid table in sync.

Second piece of code is the actual code for adding new order state and 4 statuses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
$installer = $this;
 
// Required tables
$statusTable = $installer->getTable('sales/order_status');
$statusStateTable = $installer->getTable('sales/order_status_state');
 
// Insert statuses
$installer->getConnection()->insertArray(
    $statusTable,
    array(
        'status',
        'label'
    ),
    array(
        array('status' => 'specialorder_pending', 'label' => 'Special Order Pending'),
        array('status' => 'specialorder_processing', 'label' => 'Special Order Processing'),
        array('status' => 'specialorder_complete', 'label' => 'Special Order Complete'),
        array('status' => 'specialorder_canceled', 'label' => 'Special Order Canceled')
    )
);
 
// Insert states and mapping of statuses to states
$installer->getConnection()->insertArray(
    $statusStateTable,
    array(
        'status',
        'state',
        'is_default'
    ),
    array(
        array(
            'status' => 'specialorder_pending',
            'state' => 'specialorder_state',
            'is_default' => 1
        ),
        array(
            'status' => 'specialorder_processing',
            'state' => 'specialorder_state',
            'is_default' => 0
        ),
        array(
            'status' => 'specialorder_complete',
            'state' => 'specialorder_state',
            'is_default' => 0
        ),
        array(
            'status' => 'specialorder_canceled',
            'state' => 'specialorder_state',
            'is_default' => 0
        )
    )
);

The is_default parameter isn't enough for Magento to use this new state with it's default status when your special order is created. This is because Magento's built-in payment methods like checkmo assign all new orders to built-in "new" state and "pending" status. To resolve this we need to create our own payment method that is aware of our new order state. If you are just adding order statuses to one of default Magento states, you can skip this step. With imaginary namespace Ournamespace and module name Ourmodule, here's little XML snippet to get our payment method warmed up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0"?>
 
<config>
    <!-- -->
    <default>
        <payment>
            <specialorder_payment_method>
                <active>1</active>
                <model>Ournamespace_Ourmodule_Model_Payment_Specialorder</model>
                <order_status>specialorder_pending</order_status>
                <allowspecific>0</allowspecific>
                <payment_action>init</payment_action>
                <group>offline</group>
            </specialorder_payment_method>
        </payment>
    </default>
    <!-- -->
</config>

Here comes the payment method code that goes into app/code//Ournamespace/Ourmodule/Model/Payment/Specialorder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
 
class Ournamespace_Ourmodule_Model_Payment_Specialorder extends Mage_Payment_Model_Method_Abstract
{
    protected $_code = 'specialorder_payment_method';
    protected $_canUseCheckout = false;
    protected $_isInitializeNeeded = true;
 
    /**
     * Update order state to system configuration
     *
     * @return Mage_Payment_Model_Method_Abstract
    */
    public function initialize($action, $stateObject)
    {
        if(($status = $this->getConfigData('order_status'))) {
            $stateObject->setStatus($status);
            $state = $this->_getAssignedState($status);
            $stateObject->setState($state);
            $stateObject->setIsNotified(true);
        }
        return $this;
    }
 
    /**
     * Get the assigned state of an order status
     *
     * @param string order_status
     */
    protected function _getAssignedState($status)
    {
        $item = Mage::getResourceModel('sales/order_status_collection')
            ->joinStates()
            ->addFieldToFilter('main_table.status', $status)
            ->getFirstItem();
 
        return $item->getState();
    }
 
}

As you can see in the preceding code listing, any order placed using this payment method (referenced using specialorder_payment_method) will go trough initialize() function and will have it's state and status set to default order status and first order's state this status is assigned to. If your status is assigned to multiple order states, you can hardcode desired order state code instead of using _getAssignedState() function to obtain this code.

That's all for today, until next time I wish you all happy coding!

DevGenii

E-commerce is a breeze with Magento Certified Developer Plus & Zend Certified PHP Engineer nearby. Get in touch!

7 thoughts on “Programmatically add new order state and status in Magento

  1. Matthias

    When you miss your new state / status in admin dropdowns (e.g. for “new order status” @ payment information), add the status nodes to your config.xml.

    Specialorder status

    Specialorder

    Reply
    1. Matthias

      Haha, searched for the creation of status and state programmatically and found this article. Implemented everything and it worked fine. Than I thought it would be nice to leave a thankyou-comment and found another comment of mine 🙂

      Well done, again!

      Reply
  2. Laura

    Such modifications seem to me harder when I’m trying to make them. So I prefer ready solutions. For order status change I’m using order status by Amasty.

    Reply
    1. Marko Author

      Hi Laura,
      amasty extensions are great, but this article is geared towards developers because adding custom order statuses is often just one piece of a bigger puzzle while implementing custom features.

      Regards

      Reply
  3. sandeep

    Hi

    I see your module but you didn’t share where top upload files .
    The grid parameter is what instructs Magento to keep this new attribute and it’s clone from sales_flat_order_grid table in sync.

    please share file directory so i can use it ..

    Thanks

    Reply
  4. Nicky

    Hi,
    this post is very good but I didn’t found in wich files must be written these codes? startSetup();

    $installer->addAttribute(
    'order',
    'is_specialorder',
    array(
    'type' => 'int',
    'default' => 0,
    'grid' => true,
    'unsigned' => true,
    )
    );

    $installer->endSetup();

    and another examples.
    please text me as soon as possible at the e-mail. Thanks a lot.

    Best Regards.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *