I'll give it a shot. Let's take them one at a time:
Method 1
$converter=Mage::getModel('sales/convert_order');
$shipment=$converter->toShipment($order);
$converter
above is loaded from the class Mage_Sales_Model_Convert_Order
, which uses a core helper called copyFieldset
to copy order details into a shipment object. $order has to be of type array or Varien_Object
.
This method is actually at the core of Method 3, as it uses Mage::getModel('sales/convert_order')
in its constructor call.
Key differentiator of this method - it can take an array or an object $order
and generate a basic $shipment
object. It is a lower-level method used exclusively by the methods you put forth in Method 2, Method 3.
Method 2
$shipment = Mage::getModel('sales/service_order', $order)
->prepareShipment($this->_getItemQtys($order));
This seems to be the most popular way in Magento's Core of generating a shipment as it is used in both Shipment and Invoice controllers. $order
is used as a constructor argument to the instantiation of Mage_Sales_Model_Service_Order
, setting it as a protected property on the object.
You're then calling prepareShipment
and passing a quantity. As this method uses the converter class from Method 1, you needn't specify more details such as order items pass item shipment qty details in the prepareShipment
argument, called here with $this->_getItemQtys
. To use this on your own context, all you need to do is pass the quantity of items in an array with the following format:
array(
'order_item_id'=>$qty,
'order_item_id'=>$qty,
'order_item_id'=>$qty
)
Key differentiator of this method - it gives you back a $shipment object, but with all items converted on it. It's plug-and-play.
Method 3
I could not find evidence of using this method in the Core. It looks like a hack, to be honest. Here's the method:
$itemQty = $order->getItemsCollection()->count();
$shipment = Mage::getModel('sales/service_order', $order)->prepareShipment($itemQty);
$shipment = new Mage_Sales_Model_Order_Shipment_Api();
$shipmentId = $shipment->create($orderId);
Step 1 is exactly the same as Method 2 above. No difference. However, you get back a $shipment
object, which is replaced by a direct insantiation of Mage_Sales_Model_Order_Shipment_Api
. This is non-standard. The best-practice way of getting a shipment Api object would be to call Mage::getModel('sales/order_shipment_api')
.
Next, it uses that overwritten, new Shipment API object to create a shipment from an $orderId
variable that hasn't been defined in your code. Again, this seems like a workaround.
Looking at Mage_Sales_Model_Order_Shipment_Api::create()
, it seems like a one-stop-shop for generating a shipment as the most basic details needed to create the shipment is only an order increment_id
.
This is a hack that shouldn't be used by any module or extension. This API is meant to be consumed by features exposed via XML RPC / SOAP API requests and is intentionally basic to eliminate multiple step API requests.
Eventually Method 3 gets to the nitty-gritty, though, and via a call to Mage_Sales_Model_Order, it calls prepareShipment
, which is a higher-order abstraction for the familiar Method 2 above:
public function prepareShipment($qtys = array())
{
$shipment = Mage::getModel('sales/service_order', $this)->prepareShipment($qtys);
return $shipment;
}
Key differentiator here - if you need a shipment, don't mind hacks, and only have an increment_id - use this method. Also useful information if you prefer to handle this via the SOAP API.
I hope that helps.
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:
Best Answer
Timeout means, something needs to long :-)
Check the HTTP response, is there any? This error messages looks like generated by the browser, so the question is, what happend on HTTP level.