-
Notifications
You must be signed in to change notification settings - Fork 248
Source Selection Algorithm
- Source Selection Algorithm Introduction
- Decoupling SSA from Order object
- Adding new Algorithm by 3rd party developer
- How Magento deals with SSA
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).
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 frontend 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 SourceSelectionAlgorithmInterface[]
*/
public function execute();
}
/**
* Data Interface representing particular Source Selection Algorithm
*
* @api
*/
interface SourceSelectionAlgorithmInterface
{
/**
* @return string
*/
public function getCode(): string;
/**
* @return string
*/
public function getTitle(): string;
}
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
): SourceSelectionResultInterface;
{
// 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
): SourceSelectionResultInterface
{
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>
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
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
}
}
Multi-Source Inventory developed by Magento 2 Community
- Technical Vision. Catalog Inventory
- Installation Guide
- List of Inventory APIs and their legacy analogs
- MSI Roadmap
- Known Issues in Order Lifecycle
- MSI User Guide
- 2.3 LIVE User Guide
- MSI Release Notes and Installation
- Overview
- Get Started with MSI
- MSI features and processes
- Global and Product Settings
- Configure Source Selection Algorithm
- Create Sources
- Create Stock
- Assign Inventory and Product Notifications
- Configure MSI backorders
- MSI Import and Export Product Data
- Mass Action Tool
- Shipment and Order Management
- CLI reference
- Reports and MSI
- MSI FAQs
- DevDocs Documentation
- Manage Inventory Management Modules (install/upgrade info)
- Inventory Management
- Reservations
- Inventory CLI reference
- Inventory API reference
- Inventory In-Store Pickup API reference
- Order Processing with Inventory Management
- Managing sources
- Managing stocks
- Link and unlink stocks and sources
- Manage source items
- Perform bulk actions
- Manage Low-Quantity Notifications
- Check salable quantities
- Manage source selection algorithms
- User Stories
- Support of Store Pickup for MSI
- Product list assignment per Source
- Source assignment per Product
- Stocks to Sales Channel Mapping
- Adapt Product Import/Export to support multi Sourcing
- Introduce SourceCode attribute for Source and SourceItem entities
- Assign Source Selector for Processing of Returns Credit Memo
- User Scenarios:
- Technical Designs:
- Module Structure in MSI
- When should an interface go into the Model directory and when should it go in the Api directory?
- Source and Stock Item configuration Design and DB structure
- Stock and Source Configuration design
- Open Technical Questions
- Inconsistent saving of Stock Data
- Source API
- Source WebAPI
- Sources to Sales Channels mapping
- Service Contracts MSI
- Salable Quantity Calculation and Mechanism of Reservations
- StockItem indexation
- Web API and How To cover them with Functional Testing
- Source Selection Algorithms
- Validation of Domain Entities
- PHP 7 Syntax usage for Magento contribution
- The first step towards pre generated IDs. And how this will improve your Integration tests
- The Concept of Default Source and Domain Driven Design
- Extension Point of Product Import/Export
- Source Selection Algorithm
- SourceItem Entity Extension
- Design Document for changing SerializerInterface
- Stock Management for Order Cancelation
- Admin UI
- MFTF Extension Tests
- Weekly MSI Demos
- Tutorials