Skip to content

Source Selection Algorithm

Igor Miniailo edited this page Mar 1, 2018 · 36 revisions

Table of Contents

Source Selection Algorithm Introduction

When the system needs to determine from which Sources the delivery should be done - Source Selection Algorithm (SSA) has to be launched.

The main goal of SSA is to receive the total number of requested SKUs and their quantities, as well as the shipping address (the destination where the products should be shipped to), and based on particular business need which could be configured by merchant (organize delivery from Sources with Highest priority first or Minimal delivery cost etc.) algorithm should provide a list of Source Items with Quantities to deduct per each Source Item.

Based on results provided by SSA, Magento makes a deduction for each Source Item with specified quantity. A merchant can modify provided results adjusting quantities for deduction or even re-assigning sources from which delivery happen. That's why we never save results of Source Selection Algorithm as the results make sense just for given point of time, and can get invalid after some time (when stock data changed on source basis).

Decoupling SSA from Order object

Minimal Viable Product (MVP) of MSI will provide Source Deduction at the time when Order has already been placed and Merchant creates Shipping for the given Order. That's why current implementation is dealing with Order object from which all needed data is retrieved - ShippingAlgorithmInterface.

namespace Magento\InventoryShipping\Model;

/**
 * Returns shipping algorithm result for the order (extension point, SPI)
 *
 * @api
 */
interface ShippingAlgorithmInterface
{
    /**
     * @param OrderInterface $order
     * @return ShippingAlgorithmResultInterface
     */
    public function execute(OrderInterface $order): ShippingAlgorithmResultInterface;
}

But that's not flexible as potentially some could want to introduce customization and launch algorithm at the time when Order is being placed. For example, on fronted when customer proceeds to checkout. The goal of this move is to provide more accurate shipping cost to charge a customer. In this case Order object is not created yet, thus we can't use it as input data for SSA, the system should deal with Quote object instead.

Taking into account that there could be at least two valid business cases when SSA launched, and the source of data is different (Order and Quote) - it makes sense to introduce a new layer of Abstraction and make the algorithm use abstract data container, but not particular Magento entity.

/**
 * Request products in a given Qty to be delivered to Shipping Address
 */
interface InventoryRequestInterface
{
    /**
     * @return int
     */
    public function getStockId();

    /**
     * @return \Magento\InventoryShipping\Api\Data\ShippingAddressInterface
     */
    public function getShippingAddress();

    /**
     * @return \Magento\InventoryShipping\Api\Data\ItemRequestInterface[]
     */
    public function getItems();
}

/**
 * Represents requested quantity for particular product
 */
interface ItemRequestInterface
{
    /**
     * Requested SKU
     *
     * @return string
     */
    public function getSku();

    /**
     * Requested Product Quantity
     *
     * @return float
     */
    public function getQty();
}

After these changes Source Selection Algorithm service should be adjusted like this:

/**
 * Returns source selection algorithm result for given Inventory Request 
 *
 * @api
 */
interface SourceSelectionServiceInterface
{
    /**
     * @param InventoryRequestInterface $inventoryRequest
     * @param string $algorithmCode    BY_PRIORITY | MINIMAL_COST | etc
     * @return SourceSelectionResultInterface
     */
    public function execute(
       InventoryRequestInterface $inventoryRequest,
       string $algorithmCode
    ): SourceSelectionResultInterface;
}

SSA Provider should be adjusted accordingly to return all the registered algorithms

/**
 * Returns the list of Data Interfaces which represent registered SSA in the system
 *
 * @api
 */
interface SourceSelectionAlgorithmProviderInterface
{
    /**
     * @return SourceSelectionAlgorithm[]
     */
    public function execute(): ShippingAlgorithmInterface;
}

/**
 * Data Interface representing particular Source Selection Algorithm
 *
 * @api
 */
interface SourceSelectionAlgorithm
{
    /**
     * @return string
     */
    public function getCode(): string;

    /**
     * @return string
     */
    public function getTitle(): string;
}

Adding new Algorithm by 3rd party developer

To add new Algorithm 3PD should register it in the system. To do so, he needs to provide own implementation for the interface.

/**
 * Returns source selection algorithm result for given Inventory Request 
 * Current interface should be implemented in order to add own Source Selection Method
 * 
 * @api
 */
interface SourceSelectionInterface
{
    /**
     * @param InventoryRequestInterface $inventoryRequest
     * @return SourceSelectionResultInterface
     */
    public function execute(
       InventoryRequestInterface $inventoryRequest
    ): SourceSelectionResultInterface;
}

like this:

namespace Some\Vendor\Namespace\SourceSelection;

/**
 * Minimal Delivery Cost for Merchant algorithm
 *
 * @api
 */
class MinimalDeliveryCostAlgorithm implements SourceSelectionInterface
{
    public function execute(
       InventoryRequestInterface $inventoryRequest
    ): SourceSelectionAlgorithmResultInterface;
    {
        // TODO: Implement execute() method.
    }
}

After that 3PD suppose to provide new SSA to the SourceSelectionServiceInterface implementation to make it aware of all the possible methods. This should be done via DI configuration.

    <type name="Magento\InventoryShipping\Model\SourceSelectionService">
        <arguments>
            <argument name="sourceSelectionMethods" xsi:type="array">
                <item name="priority" xsi:type="string">Magento\InventoryShipping\Model\PriorityBasedAlgorithm</item>
                <item name="minimalDeliveryCost" xsi:type="string">Some\Vendor\Namespace\SourceSelection\MinimalDeliveryCostAlgorithm</item>
            </argument>
        </arguments>
    </type>
class SourceSelectionService implements SourceSelectionServiceInterface
{
    /**
     * @param array
     */
    public function __construct(
        array $sourceSelectionMethods,
        \Magento\Framework\ObjectManagerInterface $objectManager
    ) {
        $this->sourceSelectionMethods = $sourceSelectionMethods;
        $this->objectManager = $objectManager;
    }


    public function execute(
        InventoryRequestInterface $inventoryRequest,
        string $algorithmCode
    ): SourceSelectionAlgorithmResultInterface
    {
       if (!isset($this->sourceSelectionMethods[$algorithmCode])) {
            throw new \LogicException(
                'There is no such Source Selection Algorithm implemented: ' . $algorithmCode
            );
        }
        $sourceSelectionClassName = $this->sourceSelectionMethods[$algorithmCode];

        $sourceSelectionAlgorithm = $this->objectManager->create($sourceSelectionClassName);
        if (false === $sourceSelectionAlgorithm instanceof SourceSelectionInterface) {
            throw new \LogicException(
                $sourceSelectionClassName . ' doesn\'t implement SourceSelectionInterface'
            );
        }
        return $sourceSelectionAlgorithm->execute($inventoryRequest);
    }
}

Algorithm Provider should be configured the same way - to show all available Algorithms in the system

    <type name="SourceSelectionAlgorithmProvider">
        <arguments>
            <argument name="availableAlgorithms" xsi:type="array">
                <item name="priority" xsi:type="array">
                    <item xsi:type="string" name="code">priority</item>
                    <item xsi:type="string" name="title">Algorithm which provides Source Selections based on predefined priority of Source</item>
                </item>
                <item name="minimalDeliveryCost" xsi:type="array">
                    <item xsi:type="string" name="code">minimalDeliveryCost</item>
                    <item xsi:type="string" name="title">Algorithm which provides Shipping option with minimal delivery cost for Merchant</item>
                </item>
            </argument>
        </arguments>
    </type>

How Magento deals with SSA

As it was mentioned above, Magento out of the box will provide the functionality of determining Sources to fulfill the Order at the time when Shipment created for an existing order.
UI Mockup - https://magento.invisionapp.com/share/H9FM70D7QCU#/screens/279334129_Shipment Source Slecetion Algorithm Admin UI

So, InventoryRequest data (which serves as Input for Source Selection Algorithm), should be retrieved from the Order object. For these purposes, we need to introduce InventoryRequestFactory which creates InventoryRequestFactory based on inbound Order object.

class InventoryRequestFactory
{
    /**
     * @param \Magento\Sales\Api\Data\OrderInterface $order
     * @return InventoryRequestInterface
     */
    public function create(\Magento\Sales\Api\Data\OrderInterface $order) : InventoryRequestInterface 
    {
        // TODO
    }
}

If 3PD would like to customize Inventory in future and decide to add Source Selection Algorithm at the time of checkout - he needs to introduce own factory, which produces InventoryRequestInterface based on Quote object. Like this:

class InventoryRequestFactory
{
    /**
     * @param \Magento\Quote\Api\Data\CartInterface $quote
     * @return InventoryRequestInterface
     */
    public function create(\Magento\Quote\Api\Data\CartInterface $quote) : InventoryRequestInterface 
    {
        // TODO
    }
}

MSI Documentation:

  1. Technical Vision. Catalog Inventory
  2. Installation Guide
  3. List of Inventory APIs and their legacy analogs
  4. MSI Roadmap
  5. Known Issues in Order Lifecycle
  6. MSI User Guide
  7. DevDocs Documentation
  8. User Stories
  9. User Scenarios:
  10. Technical Designs:
  11. Admin UI
  12. MFTF Extension Tests
  13. Weekly MSI Demos
  14. Tutorials
Clone this wiki locally