I have an menu option to create multiple shipments from selected orders in an extension.
It works on some stores (tested) but the store I'm trying to get it to work on (2.3.1) uses MSI and I'm wondering if it's something to do with that?
 protected function massAction(AbstractCollection $collection)
{ 
 $countShipOrder = 0;
 foreach ($collection->getItems() as $order) 
 { 
 try 
 { 
 $shipOrder = $this->_shipOrder->create();
 $ship = $shipOrder->shipOrder($order->getId());
 $countShipOrder++; 
 }
 catch (\Exception $e) 
 {
 $this->messageManager->addError(__('Order Shipments Already Created For : %1',$order['increment_id']));
 } 
 }
 $countNonShipOrder = $collection->count() - $countShipOrder;
 if ($countShipOrder) {
 $string = $countShipOrder.' success, '.$countNonShipOrder .' fails';
 $this->messageManager->addSuccess(__('Order shipments processed : %1.', $string));
 }
 $this->_redirect('sales/order/index');
}
class ShipOrder extends \Magento\Framework\Model\AbstractModel
{ 
 protected $_scopeConfig;
 protected $_order;
 protected $_orderConverter;
 protected $eventManager;
 public function __construct(
 \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
 \Magento\Sales\Model\OrderFactory $order,
 \Magento\Sales\Model\Convert\OrderFactory $orderConverter,
 \Magento\Framework\Event\Manager $eventManager,
 \Magento\Framework\Model\Context $context
 ){
 $this->_scopeConfig = $scopeConfig;
 $this->_order = $order;
 $this->eventManager = $eventManager;
 $this->_orderConverter = $orderConverter;
 }
 public function shipOrder($orderId)
 {
 //load by order 
 $order = $this->_order->create()->load($orderId);
 // Check if order can be shipped or has already shipped
 if ( !$order->canShip()) {
 throw new \Magento\Framework\Exception\LocalizedException(
 __('You can\'t create an shipment for order increment id: %1', $order->getIncrementId())
 );
 return false;
 }
 else {
 $shipment = $this->createShipment($order);
 return $shipment;
 }
 }
 public function createShipment($order){
 // Initialize the order shipment object
 $convertOrder = $this->_orderConverter->create();
 $shipment = $convertOrder->toShipment($order);
 // Loop through order items
 foreach ($order->getAllItems() as $orderItem) {
 // Check if order item has qty to ship or is virtual
 if (! $orderItem->getQtyToShip() || $orderItem->getIsVirtual()) {
 continue;
 }
 $qtyShipped = $orderItem->getQtyToShip();
 // Create shipment item with qty
 $shipmentItem = $convertOrder->itemToShipmentItem($orderItem)->setQty($qtyShipped);
 // Add shipment item to shipment
 $shipment->addItem($shipmentItem);
 }
 // Register shipment
 $shipment->register();
 $shipment->getOrder()->setIsInProcess(true);
 try {
 // Save created shipment and order
 $shipment->save();
 $shipment->getOrder()->save();
 return $shipment;
 } catch (\Exception $e) {
 throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()));
 }
 }
That final try catch where it tries to save the shipment is where the error is being thrown.
The undefined variable sourceCode is coming from /vendor/magento/module-inventory-shipping/Observer/SourceDeductionProcessor.php on line 112
2 Answers 2
As MSI has been implemented, in your code you have to specify the source location.
Add the following line before $shipment save()
$shipment->getExtensionAttributes()->setSourceCode('default');
$source = '';
$product = $this->_productFactory->create()->load($productId);
$_sources = $this->getSourceItemDetailBySKU($product->getSku());
if(empty($source)) {
 if (count($_sources) > 0) {
 foreach ($_sources as $_source) {
 $dataSource = $_source->getData();
 $qty = (int)$dataSource['quantity'];
 if((int)$dataSource['quantity'] == 0){ continue ;}
 if (empty($arraySource[$dataSource['source_code']])) {
 $arraySource[$dataSource['source_code']] = 1;
 } else {
 $arraySource[$dataSource['source_code']] = (int)$arraySource[$dataSource['source_code']] + 1;
 }
 if ($arraySource[$dataSource['source_code']] == count($order->getAllItems())) {
 $source = $dataSource['source_code'];
 break;
 }
 }
 }
}
$shipment->getExtensionAttributes()->setSourceCode($source);
sourceCodereferences?$this->isSingleSourceMode->execute()and$shipment->getExtensionAttributes()fail the conditions therefore the$sourceCodevariable is never initialise. You will need to find out why both of those fail. Use a debugger and trace it down.