Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#518 Simplest “By Priority” Algorithm implementation #560

Merged
merged 26 commits into from
Mar 1, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f3bbc5f
#518 [WIP] Loaded sources by priority
Feb 15, 2018
0b22472
Merge branch 'develop' into 518-priority-adjustments-simple-selection…
Feb 17, 2018
68694a8
#518 Refactored the default shipping selection algo to process source…
Feb 17, 2018
131fc1c
#518 Put all variable assignments in the same places
Feb 17, 2018
32ffc90
#518 Refactored shipping selection algo classname
Feb 17, 2018
74a08e4
#518 Changed the epsilon
Feb 17, 2018
adfa7f7
#518 Added the message if order items can’t be delivered by the curre…
Feb 17, 2018
1f3cf0b
[TASK] #518 Commented the algorithm
Feb 17, 2018
9adcaec
#518 Renamed the classname
Feb 17, 2018
39ca211
#518 Made the tab hidden if source items are not enough to deliver th…
Feb 17, 2018
2101841
Merge branch 'develop' into 518-priority-adjustments-simple-selection…
Feb 22, 2018
8639184
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 23, 2018
4690f24
Merge remote-tracking branch 'origin/518-priority-adjustments-simple-…
p-bystritsky Feb 23, 2018
9de498f
Merge branch '519_Adatp_UI_according_to_Single_or_Multi_Stocks' into …
Feb 23, 2018
2e15fda
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 23, 2018
0dcfc62
Merge remote-tracking branch 'origin/518-priority-adjustments-simple-…
p-bystritsky Feb 23, 2018
28ec1f8
Merge branch 'develop' into 518-priority-adjustments-simple-selection…
Feb 26, 2018
a6962b6
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 26, 2018
0fb5f25
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 26, 2018
54add71
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 27, 2018
1d8d603
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 28, 2018
1a6b455
518: Simplest “By Priority” Algorithm implementation
p-bystritsky Feb 28, 2018
e9d7682
Merge remote-tracking branch 'origin/develop' into 518-priority-adjus…
Mar 1, 2018
11364c5
MSI-518: Simplest “By Priority” Algorithm implementation
Mar 1, 2018
1e3752a
MSI-518: Simplest “By Priority” Algorithm implementation
Mar 1, 2018
4a18953
MSI-518: Simplest “By Priority” Algorithm implementation
Mar 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Magento\InventoryApi\Api\GetStockSourceLinksInterface;
use Magento\InventoryApi\Api\SourceRepositoryInterface;
use Psr\Log\LoggerInterface;
use Magento\Framework\Api\SortOrderBuilder;

/**
* @inheritdoc
Expand All @@ -41,22 +42,30 @@ class GetAssignedSourcesForStock implements GetAssignedSourcesForStockInterface
*/
private $logger;

/**
* @var SortOrderBuilder
*/
private $sortOrderBuilder;

/**
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param SourceRepositoryInterface $sourceRepository
* @param GetStockSourceLinksInterface $getStockSourceLinks
* @param SortOrderBuilder $sortOrderBuilder
* @param LoggerInterface $logger
*/
public function __construct(
SearchCriteriaBuilder $searchCriteriaBuilder,
SourceRepositoryInterface $sourceRepository,
GetStockSourceLinksInterface $getStockSourceLinks,
SortOrderBuilder $sortOrderBuilder,
LoggerInterface $logger
) {
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->sourceRepository = $sourceRepository;
$this->getStockSourceLinks = $getStockSourceLinks;
$this->logger = $logger;
$this->sortOrderBuilder = $sortOrderBuilder;
}

/**
Expand Down Expand Up @@ -88,8 +97,13 @@ public function execute(int $stockId): array
*/
private function getAssignedSourceCodes(int $stockId): array
{
$sortOrder = $this->sortOrderBuilder
->setField(StockSourceLinkInterface::PRIORITY)
->setAscendingDirection()
->create();
$searchCriteria = $this->searchCriteriaBuilder
->addFilter(StockSourceLinkInterface::STOCK_ID, $stockId)
->addSortOrder($sortOrder)
->create();
$searchResult = $this->getStockSourceLinks->execute($searchCriteria);

Expand Down
202 changes: 145 additions & 57 deletions app/code/Magento/InventoryShipping/Model/DefaultShippingAlgorithm.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,32 @@

namespace Magento\InventoryShipping\Model;

use Magento\Inventory\Model\SourceItem\Command\GetSourceItemsBySkuInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Inventory\Model\SourceItem\Command\GetSourceItemsBySkuInterfffface;
use Magento\InventoryApi\Api\Data\SourceInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory;
use Magento\InventoryApi\Api\GetAssignedSourcesForStockInterface;
use Magento\InventoryApi\Api\SourceItemRepositoryInterface;
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
use Magento\InventorySalesApi\Api\StockResolverInterface;
use Magento\InventoryShipping\Model\ShippingAlgorithmResult\ShippingAlgorithmResultInterface;
use Magento\InventoryShipping\Model\ShippingAlgorithmResult\ShippingAlgorithmResultInterfaceFactory;
use Magento\InventoryShipping\Model\ShippingAlgorithmResult\SourceItemSelectionInterfaceFactory;
use Magento\InventoryShipping\Model\ShippingAlgorithmResult\SourceSelectionInterfaceFactory;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderItemInterface;
use Magento\Sales\Model\Order\Item as OrderItem;
use Magento\Store\Api\WebsiteRepositoryInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
* {@inheritdoc}
*
* This shipping algorithm just iterates over all the sources one by one in no particular order
* This shipping algorithm just iterates over all the sources one by one in priority order
*/
class DefaultShippingAlgorithm implements ShippingAlgorithmInterface
class PriorityShippingAlgorithm implements ShippingAlgorithmInterface
{
/**
* @var GetSourceItemsBySkuInterface
*/
private $getSourceItemsBySku;

/**
* @var SourceSelectionInterfaceFactory
*/
Expand All @@ -42,93 +49,174 @@ class DefaultShippingAlgorithm implements ShippingAlgorithmInterface
private $shippingAlgorithmResultFactory;

/**
* @var bool
* @var StoreManagerInterface
*/
private $storeManager;

/**
* @var WebsiteRepositoryInterface
*/
private $websiteRepository;

/**
* @var StockResolverInterface
*/
private $stockResolver;

/**
* @var GetAssignedSourcesForStockInterface
*/
private $getAssignedSourcesForStock;

/**
* @var SourceItemRepositoryInterface
*/
private $isShippable;
private $sourceItemRepository;

/**
* @var SearchCriteriaBuilder
*/
private $searchCriteriaBuilder;
/**
* @var SourceItemInterfaceFactory
*/
private $sourceItemFactory;

/**
* @param GetSourceItemsBySkuInterface $getSourceItemsBySku
* @param SourceSelectionInterfaceFactory $sourceSelectionFactory
* @param SourceItemSelectionInterfaceFactory $sourceItemSelectionFactory
* @param ShippingAlgorithmResultInterfaceFactory $shippingAlgorithmResultFactory
* @param StoreManagerInterface $storeManager
* @param WebsiteRepositoryInterface $websiteRepository
* @param StockResolverInterface $stockResolver
* @param GetAssignedSourcesForStockInterface $getAssignedSourcesForStock
* @param SourceItemRepositoryInterface $sourceItemRepository
* @param SourceItemInterfaceFactory $sourceItemFactory
* @param SearchCriteriaBuilder $searchCriteriaBuilder
*/
public function __construct(
GetSourceItemsBySkuInterface $getSourceItemsBySku,
SourceSelectionInterfaceFactory $sourceSelectionFactory,
SourceItemSelectionInterfaceFactory $sourceItemSelectionFactory,
ShippingAlgorithmResultInterfaceFactory $shippingAlgorithmResultFactory
ShippingAlgorithmResultInterfaceFactory $shippingAlgorithmResultFactory,
StoreManagerInterface $storeManager,
WebsiteRepositoryInterface $websiteRepository,
StockResolverInterface $stockResolver,
GetAssignedSourcesForStockInterface $getAssignedSourcesForStock,
SourceItemRepositoryInterface $sourceItemRepository,
SourceItemInterfaceFactory $sourceItemFactory,
SearchCriteriaBuilder $searchCriteriaBuilder
) {
$this->getSourceItemsBySku = $getSourceItemsBySku;
$this->shippingAlgorithmResultFactory = $shippingAlgorithmResultFactory;
$this->sourceSelectionFactory = $sourceSelectionFactory;
$this->sourceItemSelectionFactory = $sourceItemSelectionFactory;
$this->storeManager = $storeManager;
$this->websiteRepository = $websiteRepository;
$this->stockResolver = $stockResolver;
$this->getAssignedSourcesForStock = $getAssignedSourcesForStock;
$this->sourceItemRepository = $sourceItemRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->sourceItemFactory = $sourceItemFactory;
}

/**
* @inheritdoc
*/
public function execute(OrderInterface $order): ShippingAlgorithmResultInterface
{
$this->isShippable = true;
$sourceItemSelectionsData = $this->getSourceItemSelectionsData($order);

$isShippable = true;
$sourceSelections = [];
foreach ($sourceItemSelectionsData as $sourceCode => $sourceItemSelections) {
$sourceSelections[] = $this->sourceSelectionFactory->create([
'sourceCode' => $sourceCode,
'sourceItemSelections' => $sourceItemSelections,
]);
}

$shippingResult = $this->shippingAlgorithmResultFactory->create([
'sourceSelections' => $sourceSelections,
'isShippable' => $this->isShippable
]);
return $shippingResult;
}

/**
* Key is source code, value is list of SourceItemSelectionInterface related to this source
* @param OrderInterface $order
* @return array
*/
private function getSourceItemSelectionsData(OrderInterface $order): array
{
$sourceItemSelections = [];
$storeId = $order->getStoreId();
$sources = $this->getSourcesByStoreId($storeId);

/** @var OrderItemInterface|OrderItem $orderItem */
foreach ($order->getItems() as $orderItem) {
if ($orderItem->isDeleted() || $orderItem->getParentItemId()) {
$itemSku = $orderItem->getSku();
$qtyToDeliver = $orderItem->getQtyOrdered();

if ($orderItem->isDeleted() || $orderItem->getParentItemId() || $this->isZero($qtyToDeliver)) {
continue;
}

$itemSku = $orderItem->getSku();
$sourceItems = $this->getSourceItemsBySku->execute($itemSku)->getItems();
foreach ($sources as $source) {
$sourceItem = $this->getStockItemBySku($source->getSourceCode(), $itemSku);
$sourceItemQty = $sourceItem->getQuantity();
$qtyToDeduct = min($sourceItemQty, $qtyToDeliver);

$qtyToDeliver = $orderItem->getQtyOrdered();
foreach ($sourceItems as $sourceItem) {
if ($qtyToDeliver < 0.0001) {
break;
if ($this->isZero($sourceItemQty)) {
continue;
}

$sourceItemQty = $sourceItem->getQuantity();
if ($sourceItemQty > 0) {
$qtyToDeduct = min($sourceItemQty, $qtyToDeliver);

$sourceItemSelections[$sourceItem->getSourceCode()][] = $this->sourceItemSelectionFactory->create([
$sourceSelections[] = $this->sourceSelectionFactory->create([
'sourceCode' => $sourceItem->getSourceCode(),
'sourceItemSelections' => $this->sourceItemSelectionFactory->create([
'sku' => $itemSku,
'qty' => $qtyToDeduct,
'qtyAvailable' => $sourceItemQty,
]);
]),
]);

$qtyToDeliver -= $qtyToDeduct;
}
$qtyToDeliver -= $qtyToDeduct;
}

if ($qtyToDeliver > 0.0001) {
$this->isShippable = false;
if (!$this->isZero($qtyToDeliver)) {
$isShippable = false;
}
}

return $sourceItemSelections;
return $this->shippingAlgorithmResultFactory->create([
'sourceSelections' => $sourceSelections,
'isShippable' => $isShippable
]);
}

/**
* Retrieve sources are related to current stock that are ordered by priority
*
* @param int $storeId
*
* @return SourceInterface[]
*/
private function getSourcesByStoreId(int $storeId): array
{
$store = $this->storeManager->getStore($storeId);
$website = $this->websiteRepository->getById($store->getId());
$stock = $this->stockResolver->get(SalesChannelInterface::TYPE_WEBSITE, $website->getCode());

return $this->getAssignedSourcesForStock->execute($stock->getStockId());
}

/**
* Retrieve stock item from specific source by SKU
*
* @param string $stockCode
* @param string $sku
*
* @return SourceItemInterface
*/
private function getStockItemBySku(string $stockCode, string $sku): SourceItemInterface
{
$searchCriteria = $this->searchCriteriaBuilder
->addFilter(SourceItemInterface::SOURCE_CODE, $stockCode)
->addFilter(SourceItemInterface::SKU, $sku)
->create();
$sourceItemsResult = $this->sourceItemRepository->getList($searchCriteria);

if ($sourceItemsResult->getTotalCount() > 0) {
return reset($sourceItemsResult->getItems());
}

return $this->sourceItemFactory->create();
}

/**
* Compare float number with some epsilon
*
* @param float $floatNumber
*
* @return bool
*/
private function isZero(float $floatNumber): bool
{
return $floatNumber < 0.0001;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a pity that Constant has been added just in PHP 7.2

PHP_FLOAT_EPSILON (float)
Smallest representable positive number x, so that x + 1.0 != 1.0. Available as of PHP 7.2.0.

Please, make the precision a bit less, like 0.0000001

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ public function __construct(
*/
public function execute(): ShippingAlgorithmInterface
{
return $this->objectManager->get(DefaultShippingAlgorithm::class);
return $this->objectManager->get(PriorityShippingAlgorithm::class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
namespace Magento\InventoryShipping\Test\Integration;

use Magento\InventoryCatalog\Api\DefaultSourceProviderInterface;
use Magento\InventoryShipping\Model\DefaultShippingAlgorithm;
use Magento\InventoryShipping\Model\PriorityShippingAlgorithm;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderInterfaceFactory;
use Magento\Sales\Api\Data\OrderItemInterfaceFactory;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;

class DefaultShippingAlgorithmTest extends TestCase
class PriorityShippingAlgorithmTest extends TestCase
{
/**
* @var DefaultSourceProviderInterface
Expand All @@ -33,13 +33,16 @@ class DefaultShippingAlgorithmTest extends TestCase
private $orderFactory;

/**
* @var DefaultShippingAlgorithm
* @var PriorityShippingAlgorithm
*/
private $shippingAlgorithm;

/**
* @return void
*/
protected function setUp()
{
$this->shippingAlgorithm = Bootstrap::getObjectManager()->get(DefaultShippingAlgorithm::class);
$this->shippingAlgorithm = Bootstrap::getObjectManager()->get(PriorityShippingAlgorithm::class);
$this->defaultSourceProvider = Bootstrap::getObjectManager()->get(DefaultSourceProviderInterface::class);
$this->orderFactory = Bootstrap::getObjectManager()->get(OrderInterfaceFactory::class);
$this->orderItemFactory = Bootstrap::getObjectManager()->get(OrderItemInterfaceFactory::class);
Expand Down
1 change: 1 addition & 0 deletions app/code/Magento/InventoryShipping/etc/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<module name="Magento_InventoryShipping" setup_version="1.0.0">
<sequence>
<module name="Magento_Inventory"/>
<module name="Magento_InventorySalesApi"/>
</sequence>
</module>
</config>