8

I need orders which are not shipped yet or are partially shipped. I am using below code to get orders which can be shipped.

foreach ($orderIds as $orderId) {
    $order = Mage::getModel('sales/order')->load($orderId);
    if ($order->canShip()) {
        echo "Shipping Pending";
    }
}

But I don't want to use foreach. I need something like below.

Mage::getModel('sales/order')->getCollection()
    ->addFieldToFilter('status','can_ship');
8

Let's examine the canShip method to see how it is calculated:

/**
 * Retrieve order shipment availability
 *
 * @return bool
 */
public function canShip()
{
    if ($this->canUnhold() || $this->isPaymentReview()) {
        return false;
    }

    if ($this->getIsVirtual() || $this->isCanceled()) {
        return false;
    }

    if ($this->getActionFlag(self::ACTION_FLAG_SHIP) === false) {
        return false;
    }

    foreach ($this->getAllItems() as $item) {
        if ($item->getQtyToShip()>0 && !$item->getIsVirtual()
            && !$item->getLockedDoShip())
        {
            return true;
        }
    }
    return false;
}

The order methods can be substituted as follows

  1. canUnhold()

    order->state === 'holded'
    
  2. isPaymentReview()

    order->state === 'payment_review'
    
  3. getIsVirtual()

    order->is_virtual === 1
    
  4. isCanceled()

    order->state === 'canceled'
    
  5. getActionFlag()

    Action flags are set during sales processes, not relevant for retrieving orders from the database

  6. getAllItems()

    Here we need to do a join over the order items. is_virtual and locked_do_ship are columns of the sale_flat_order_item table.

    1. getQtyToShip()

      This again is calculated based on other attributes

      /**
       * Retrieve item qty available for ship
       *
       * @return float|integer
       */
      public function getQtyToShip()
      {
          if ($this->isDummy(true)) {
              return 0;
          }
      
          return $this->getSimpleQtyToShip();
      }
      

      isDummy returns is true if parent_id === null and the product has the "ship seperately" option OR if parent_id !== null and the product doesn't have the "ship seperately" option.

      getSimpleQtyToShip returns qty_ordered - qty_shipped - qty_refunded - qty_canceled.

The code

With this information we can prepare a collection:

$collection = Mage::getModel('sales/order')->getCollection();

First, we join the items that belong to each order:

$collection->getSelect()
    ->joinLeft(
        array('order_item' => $collection->getTable('sales/order_item')),
        'main_table.entity_id=order_item.order_id', array('qty_ordered', 'qty_shipped', 'qty_refunded', 'qty_canceled', 'is_virtual', 'locked_do_ship'))
    ->group('main_table.entity_id');

Then, we filter order statuses that cannot be shipped ("nin" = "not in"):

$collection
    ->addFieldToFilter('status', array('nin' => array(
        'holded', 'payment_review', 'canceled'
    )))
    ->addFieldToFilter('main_table.is_virtual', '0');

Then, we create an SQL expression for the number of items that can be shipped:

  • we sum the shippable qty over the order items
  • for virtual items the result is 0
  • for "locked" items the result is 0
  • for all others, the result equals qty_ordered - qty_shipped - qty_refunded - qty_canceled

TODO: take product option "ship separately into account. This query will count all parent and child items, so there will be false positives. I'll leave it as an exercise to the reader to also calculate the result of isDummy() in SQL.

The sum will be available with the alias "shippable_items"

$collection->addExpressionFieldToSelect(
    'shippable_items',
    'SUM(({{qty_ordered}} - {{qty_shipped}} - {{qty_refunded}} - {{qty_canceled}}) * !{{is_virtual}} * {{locked_do_ship}} IS NOT NULL)',
    array(
        'qty_ordered' => 'order_item.qty_ordered',
        'qty_shipped' => 'order_item.qty_shipped',
        'qty_refunded' => 'order_item.qty_refunded',
        'qty_canceled' => 'order_item.qty_canceled',
        'is_virtual' => 'order_item.is_virtual',
        'locked_do_ship' => 'order_item.locked_do_ship'));

Finally we filter only orders with a positive number of shippable items. We have to use "HAVING" instead of "WHERE" because the column is calculated with an aggregate function:

$collection->getSelect()->having('shippable_items > 0'));
  • good explanation .I will check.+1 – Amit Bera Jul 1 '15 at 11:25
2

It can not possible be because of lot of conditions are check and whenever we use canShip() function.Tt too complex to do that.

It is not only depend one /two order fields depend like

  1. order status hold or not.
  2. order status cancel or not.
  3. order virtual or not.
  4. Order item is capable to do ship order

etc

So , it depends on some complex logic/condition like:

See a view on Mage_Sales_Model_Order class, and you can understand that.

 public function canShip()
    {
        if ($this->canUnhold() || $this->isPaymentReview()) {
            return false;
        }

        if ($this->getIsVirtual() || $this->isCanceled()) {
            return false;
        }

        if ($this->getActionFlag(self::ACTION_FLAG_SHIP) === false) {
            return false;
        }

        foreach ($this->getAllItems() as $item) {
            if ($item->getQtyToShip()>0 && !$item->getIsVirtual()
                && !$item->getLockedDoShip())
            {
                return true;
            }
        }
        return false;
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.