Magento – Auto-Split Shipments Based on Maximum Weight

adminadminhtmlmagento-1.7ordersshipment

Here in Australia we have AusPost who will not ship anything above 20kg. On this particular site, there are often shipments much larger comprising of a lot of smaller products, say 1kg each.

I would like to know if it is possible to extend Magento so that based on a maximum weight threshold, the shipments are automatically split into lots of 20kg packages (versus the client having to work out the QTY for each item to make the shipment match as close to the threshold weight (20kg in this example) as possible.

Ideally the solution should work with bulk order management extensions such as Better Order Management by Amasty or Simplify Bulk Order Processing by Xtento – preferably the former.

Best Answer

OK, your request is not difficult to implement, see my solution. It works only when you create shipment using Magento admin panel. I supposed that shipments are created manually.

1. Create local module (ex. ShipmentSplit) and rewrite Shipment controller, config.xml:

<?xml version="1.0"?>
<config>
    <modules>
        <Some_ShipmentSplit>
            <version>0.1.0</version>
        </Some_ShipmentSplit>
    </modules>
    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <Some_ShipmentSplit before="Mage_Adminhtml">Some_ShipmentSplit_Adminhtml</Some_ShipmentSplit>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
    <global>
        <helpers>
            <some_shipmentsplit>
                <class>Some_ShipmentSplit_Helper</class>
            </some_shipmentsplit>
        </helpers>
    </global>
</config>

2. Create Configuration settings to enable/disable splitting functionality and store weight threshold parametr, system.xml.

<?xml version="1.0"?>
<config>
    <sections>
        <shipping>
            <groups>
                <splitting translate="label">
                    <label>Shipment Splitting</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>10</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>0</show_in_website>
                    <show_in_store>0</show_in_store>
                    <fields>
                        <enable translate="label">
                            <label>Allow Shipping to Split</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>1</sort_order>
                            <show_in_default>1</show_in_default>
                        </enable>
                        <weight translate="label">
                            <label>Maximum Weight</label>
                            <frontend_type>text</frontend_type>
                            <validate>validate-number</validate>
                            <sort_order>2</sort_order>
                            <show_in_default>1</show_in_default>
                            <depends>
                                <enable>1</enable>
                            </depends>
                        </weight>
                    </fields>
                </splitting>
            </groups>
        </shipping>
    </sections>
</config>

3. Rewrite Shipment controller:

<?php
require_once 'Mage/Adminhtml/controllers/Sales/Order/ShipmentController.php';
class Some_ShipmentSplit_Adminhtml_Sales_Order_ShipmentController extends Mage_Adminhtml_Sales_Order_ShipmentController
{

    public function saveAction()
    {
        $enableSplitting = Mage::getStoreConfig('shipping/splitting/enable');
        $isNeedCreateLabel = isset($data['create_shipping_label']) && $data['create_shipping_label'];

        //if splitting not enabled use standard magento logic
        if (!$enableSplitting || $isNeedCreateLabel) return parent::saveAction();

        $data = $this->getRequest()->getPost('shipment');
        if (!empty($data['comment_text'])) {
            Mage::getSingleton('adminhtml/session')->setCommentText($data['comment_text']);
        }

        try {
            $shipment = $this->_initShipment();
            if (!$shipment) {
                $this->_forward('noRoute');
                return;
            }

            $comment = '';
            if (!empty($data['comment_text'])) {
                $shipment->addComment(
                    $data['comment_text'],
                    isset($data['comment_customer_notify']),
                    isset($data['is_visible_on_front'])
                );
                if (isset($data['comment_customer_notify'])) {
                    $comment = $data['comment_text'];
                }
            }

            $shipmentCreatedMessage = $this->__('The shipment has been created.');
            $labelCreatedMessage = $this->__('The shipping label has been created.');

            //split to groups
            $shipments = $this->_initShipmentGroups($shipment);

            foreach ($shipments as $shipment) {
                $shipment->register();
                if (!empty($data['send_email'])) {
                    $shipment->setEmailSent(true);
                }
                $shipment->getOrder()->setCustomerNoteNotify(!empty($data['send_email']));

                $this->_saveShipment($shipment);
                $shipment->sendEmail(!empty($data['send_email']), $comment);

                $this->_getSession()->addSuccess($isNeedCreateLabel ? $shipmentCreatedMessage . ' ' . $labelCreatedMessage
                    : $shipmentCreatedMessage);
                Mage::getSingleton('adminhtml/session')->getCommentText(true);
            }
        } catch (Mage_Core_Exception $e) {
            $this->_getSession()->addError($e->getMessage());
            $this->_redirect('*/*/new', array('order_id' => $this->getRequest()->getParam('order_id')));
        } catch (Exception $e) {
            Mage::logException($e);
            $this->_getSession()->addError($this->__('Cannot save shipment.'));
            $this->_redirect('*/*/new', array('order_id' => $this->getRequest()->getParam('order_id')));
        }
        $this->_redirect('*/sales_order/view', array('order_id' => $shipment->getOrderId()));
    }

    /**
     * this function splits shipment into groups
     * @param Mage_Sales_Model_Order_Shipment $shipment
     * @return array
     */
    protected function _initShipmentGroups(Mage_Sales_Model_Order_Shipment $shipment)
    {
        /**
         * @var $items Mage_Sales_Model_Resource_Order_Item_Collection
         */
        if ($maxKg = (int)Mage::getStoreConfig('shipping/splitting/weight')) {
            $qtys = $this->_getItemQtys();
            $itemWeights = array();
            $allItems = array();

            $order = $shipment->getOrder();
            $items = $shipment->getOrder()->getItemsCollection();
            /**
             * fetching items weight
             */
            foreach ($qtys as $itemId => $qty) {
                $itemWeights[$itemId] = $items->getItemById($itemId)->getWeight();
                while ($qty--) $allItems[] = $itemId;
            }

            $groups = array();
            $i = 0;
            /**
             * create item groups which every group weight up to $maxKg threshold
             */
            while (count($allItems) > 0) {
                $i++;
                $groups[$i] = array(
                    'items' => array(),
                    'weight' => 0,
                );
                foreach ($allItems as $index => $itemId) {
                    $itemWeight = $itemWeights[$itemId];
                    if ($groups[$i]['weight'] + $itemWeight <= $maxKg) {
                        $groups[$i]['weight'] += $itemWeight;
                        $groups[$i]['items'][] = $itemId;
                        unset($allItems[$index]);
                    }
                }
                /**
                 * check if an item is added to the group,
                 * if not added, it means some product weight is over threshold
                 * you can throw within if or add this product to the group
                 * or you can remove this IF block
                 * if you are sure that all products have appropriate weight
                 */
                if (count($groups[$i]['items']) == 0) {
                    $itemWeight = $itemWeights[$itemId];
                    $groups[$i]['weight'] += $itemWeight;
                    $groups[$i]['items'][] = $itemId;
                    unset($allItems[$index]);
                }
            }

            //print_r($groups);
            $shipments = array();
            if (count($groups) > 1) {
                foreach ($groups as $data) {
                    $groupItems = array();
                    foreach ($data['items'] as $itemId) {
                        if (isset($groupItems[$itemId])) {
                            /**
                             * if same item then only increase quantity
                             */
                            $groupItems[$itemId]++;
                        } else {
                            $groupItems[$itemId] = 1;
                        }
                    }
                    //create shipment for each group
                    $shipments[] = Mage::getModel('sales/service_order', $order)->prepareShipment($groupItems);
                }
            } else {
                $shipments[] = $shipment;
            }
            return $shipments;
        } else {
            Mage::throwException('Shipment package weight not defined');
        }
    }
}

Enter to admin order management page, open order, click [ship] button, click [submit shipment] button (you can except some items giving 0 qty to ship) and our logic automatically create shipments based on weight threshold (minimum one shipment). See my results:

enter image description here

Related Topic