How to Create Shipment Programmatically with MSI Magento 2

Hello Guys!

Welcome back to my blog. There are many tutorials and stack Exchange answers available for programmatically create a shipment in Magento 2. But today in this article I’ll talk about how we can programmatically create a shipment in multi stock inventory MSI in Magento 2.

For MSI we need to assign a source to shipment. So you need to create an extension factory for shipment and assign a source to shipment and then register shipment.

Here is the sample code for create shipment

<?php

namespace MagePrince\Shipment\Model\MassAction;

use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Message\ManagerInterface as MessageManagerInterface;
use Magento\Sales\Api\Data\ShipmentExtensionFactory;
use Magento\Sales\Api\Data\ShipmentItemCreationInterface;
use Magento\Sales\Api\Data\ShipmentItemCreationInterfaceFactory;
use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface;
use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator;
use Magento\Sales\Model\Order\ShipmentDocumentFactory;
use Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader;

class CreateShipment
{
    /**
     * OrderRepositoryInterface
     */
    private $orderRepository;

    /**
     * @var MessageManagerInterface
     */
    private $messageManager;

    /**
     * @var ShipmentLoader
     */
    private $shipmentLoader;

    /**
     * @var \Magento\Framework\ObjectManagerInterface
     */
    private $_objectManager;

    /**
     * @var ShipmentExtensionFactory
     */
    private $shipmentExtensionFactory;

    /**
     * @var ShipmentDocumentFactory
     */
    private $documentFactory;

    /**
     * @var ShipmentItemCreationInterfaceFactory
     */
    private $itemFactory;

    /**
     * CreateShipment constructor.
     * @param OrderRepositoryInterface $orderRepository
     * @param MessageManagerInterface $messageManager
     * @param ShipmentLoader $shipmentLoader
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param ShipmentExtensionFactory $shipmentExtensionFactory
     * @param ShipmentDocumentFactory $documentFactory
     * @param ShipmentItemCreationInterfaceFactory $itemFactory
     * @param ShipmentValidatorInterface|null $shipmentValidator
     */
    public function __construct(
        OrderRepositoryInterface $orderRepository,
        MessageManagerInterface $messageManager,
        ShipmentLoader $shipmentLoader,
        \Magento\Framework\ObjectManagerInterface $objectManager,
        ShipmentExtensionFactory $shipmentExtensionFactory,
        ShipmentDocumentFactory $documentFactory,
        ShipmentItemCreationInterfaceFactory $itemFactory,
        ShipmentValidatorInterface $shipmentValidator = null
    ) {
        $this->orderRepository = $orderRepository
        $this->messageManager = $messageManager;
        $this->shipmentLoader = $shipmentLoader;
        $this->_objectManager = $objectManager;
        $this->shipmentExtensionFactory = $shipmentExtensionFactory;
        $this->documentFactory = $documentFactory;
        $this->itemFactory = $itemFactory;
        $this->shipmentValidator = $shipmentValidator ?: \Magento\Framework\App\ObjectManager::getInstance()
            ->get(\Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface::class);
    }

    public function createShipment($orderId)
    {
        $order = $this->orderRepository->get($orderId);

        $sourceCode = 'test_source_code'; // Add source code

        $items = [];
        foreach ($order->getAllItems() as $item) {
            if (!$item->getParentItem() && !$item->getIsVirtual()) {
                $items[$item->getItemId()] = $item->getQtyToShip();
            }
        }

        try {
            $shipment = $this->loadShipment($order, $items);

            if (!$shipment) {
                return false;
            }

            $validationResult = $this->shipmentValidator->validate($shipment, [QuantityValidator::class]);

            if ($validationResult->hasMessages()) {
                $this->messageManager->addErrorMessage(
                    __("Order: %1: Shipment Document Validation Error(s):\n" .
                        implode("\n", $validationResult->getMessages()), $order->getIncrementId())
                );
                return false;
            }

            $shipmentExtension = $shipment->getExtensionAttributes();

            if (empty($shipmentExtension)) {
                $shipmentExtension = $this->shipmentExtensionFactory->create();
            }
            $shipmentExtension->setSourceCode($sourceCode);
            $shipment->setExtensionAttributes($shipmentExtension);

            $shipment->register();

            $this->_saveShipment($shipment);

            $this->messageManager->addSuccessMessage(
                __('Order %1: The shipment has been created successfully', $order->getIncrementId())
            );
        } catch (\Magento\Framework\Exception\LocalizedException $e) {
            $message = __('Order %1: %2', $order->getIncrementId(), $e->getMessage());
            $this->messageManager->addErrorMessage($message);
            return false;
        } catch (\Exception $e) {
            $message = __('Order %1: %2.', $order->getIncrementId(), $e->getMessage());
            $this->messageManager->addErrorMessage($message);
            return false;
        }
    }

    /**
     * Save shipment and order in one transaction
     *
     * @param \Magento\Sales\Model\Order\Shipment $shipment
     * @return $this
     */
    protected function _saveShipment($shipment)
    {
        $shipment->getOrder()->setIsInProcess(true);
        $transaction = $this->_objectManager->create(
            \Magento\Framework\DB\Transaction::class
        );
        $transaction->addObject(
            $shipment
        )->addObject(
            $shipment->getOrder()
        )->save();

        return $this;
    }

    /**
     * @param \Magento\Sales\Model\Order $order $order
     * @return false|\Magento\Sales\Api\Data\ShipmentInterface|\Magento\Sales\Model\Order\Shipment
     */
    public function loadShipment($order,  $items)
    {
        /**
         * Check shipment is available to create separate from invoice
         */
        if ($order->getForcedShipmentWithInvoice()) {
            $this->messageManager->addErrorMessage(
                __('Order %1: Cannot do shipment for the order separately from invoice.', $order->getIncrementId())
            );
            return false;
        }

        /**
         * Check shipment create availability
         */
        if (!$order->canShip()) {
            $this->messageManager->addErrorMessage(
                __('Order %1: Cannot do shipment for the order.', $order->getIncrementId())
            );
            return false;
        }

        $shipmentItems = $this->getShipmentItems($items);

        $shipment = $this->documentFactory->create(
            $order,
            $shipmentItems,
            []
        );

        return $shipment;
    }

    /**
     * Extract product id => product quantity array from shipment data.
     *
     * @param array $items
     * @return int[]
     */
    private function getShipmentItems(array $items)
    {
        $shipmentItems = [];
        $itemQty = isset($items) ? $items : [];
        foreach ($itemQty as $itemId => $quantity) {
            /** @var ShipmentItemCreationInterface $item */
            $item = $this->itemFactory->create();
            $item->setOrderItemId($itemId);
            $item->setQty($quantity);
            $shipmentItems[] = $item;
        }
        return $shipmentItems;
    }
}

So now just use the above class and call function createShipment($orderId) with order Id.

If you have any questions feel free to comment below.

Thanks for reading. Keep sharing 🙂