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:
All you need to do is, invoice and ship only the item that the customer needs. Once that is done, you need to cancel the order. It will turn the order status to complete and mentioning the other items are cancelled. Please check the attached
Best Answer
Yes, it's possible.
Manually
Go to the order, generate shipment, and then enter the # of qty/items to ship in that particular shipment.
The order will remain in Processing or your custom status until all items have been shipped before it will be marked as Completed.
Programmatically
There are several ways to check if an item has been shipped
1) Look at the order item's
qty_shipped
value:2) Look at the order item status:
To get more information on how a status is determined (it's not a stored value, it's derived) see this class:
https://github.com/LokeyCoding/magento-mirror/blob/magento-1.7/app/code/core/Mage/Sales/Model/Order/Item.php#L194
More reading about generating shipments programmatically:
Programmatically Creating Shipments