One of our clients for which we built an AbleCommerce based E-Commerce site needed the ability to have certain items ship as a completely separate shipment from any other items in the users order, including if the user order multiples of the same item. AbleCommerce doesn't implement this functionality so we had to customize some of the existing functionality...
We take advantage of an AbleCommerce setting in order to do this. Each product has a "Shippable" setting and that setting can either be: No, Yes, or Separately. Our code puts any product with "Separately" specified in it's own shipment. The default behavior for AbleCommerce is to treat products with Separately specified as shipping in a separate box but not as a separate shipment and if there are 2 of the same items, they won't even be calculated as being in a separate box.
I defined these functions in App_Code/BasketHelper.cs
public static void BreakupSeparateShipments(ref Basket basket)
{
//returns a list of all shipments, having it as a separate list lets us manipulate basket.Shipments without breaking the enumerator
var list = basket.Shipments.FindAll(x => x.Items.Count > 1);
foreach (BasketShipment bs1 in list)
{
var itemList = bs1.Items.FindAll(x => x.Shippable == Shippable.Separately);
//iterate through items that ship separately and put them into separate shipments
foreach (BasketItem bi in itemList.Where(item => item.Shippable == Shippable.Separately))
{
if (itemList.Count > 1 || bi.Quantity > 1)
{
if (bi.Quantity > 1)
{
int q = bi.Quantity;
bi.Quantity = 1;
bi.Save();
//Move all but one of the items to a separate shipment
for (int i = 0; i < q - 1; i++)
{
var newBi = bi.Clone();
newBi.Quantity = 1;
newBi.BasketId = basket.BasketId;
newBi.Save();
foreach (BasketItemInput _BasketInput in bi.Inputs)
{
BasketItemInput newInput = new BasketItemInput();
newInput.BasketItemId = newBi.BasketItemId;
newInput.InputFieldId = _BasketInput.InputFieldId;
newInput.InputValue = _BasketInput.InputValue;
newInput.Save();
}
basket.Items.Add(newBi);
basket.Save();
PutItemInSeparateShipment(newBi, bs1, ref basket);
newBi.ParentItemId = newBi.BasketItemId;
}
}
else
{
PutItemInSeparateShipment(bi, bs1, ref basket);
}
//if (bs1.Items.Count == 0)
// basket.Shipments.Remove(bs1);
//bs1.Save();
}
}
}
basket.Recalculate();
basket.Save();
}
public static void PutItemInSeparateShipment(BasketItem bi, BasketShipment bs1, ref Basket basket)
{
BasketShipment newShipment = new BasketShipment();
newShipment.BasketId = basket.BasketId;
newShipment.WarehouseId = bs1.WarehouseId;
newShipment.AddressId = bs1.AddressId;
newShipment.ShipMethodId = bs1.ShipMethodId;
newShipment.Save();
basket.Shipments.Add(newShipment);
//newShipment.Items.Add(bi);
// assign this basket item to the new shipment
bi.BasketShipmentId = newShipment.BasketShipmentId;
bi.Save();
// reset the parentitemid value since now this item is its own basket item record
bi.ParentItemId = bi.BasketItemId;
bi.Save();
}
I then the call BreakupSeparateShipments from the Page_Load method of the ShipMethodPage.ascx.cs but if you're using the OnePageCheckout method, you'll want to call this from the basket initialization area of OnePageCheckout.ascx.cs.
Acknowledgements: Joe Payne provided some insight for this approach and I was able to use some of his code from his post for shipping all items in an order separately in order to resolve some issues I had with my code.