diff --git a/app/code/Magento/Inventory/Model/StockSourceLink/Command/GetAssignedSourcesForStock.php b/app/code/Magento/Inventory/Model/Source/Command/GetSourcesAssignedToStockOrderedByPriority.php similarity index 64% rename from app/code/Magento/Inventory/Model/StockSourceLink/Command/GetAssignedSourcesForStock.php rename to app/code/Magento/Inventory/Model/Source/Command/GetSourcesAssignedToStockOrderedByPriority.php index 615a96a8c41a..9a9ddd76ad1e 100644 --- a/app/code/Magento/Inventory/Model/StockSourceLink/Command/GetAssignedSourcesForStock.php +++ b/app/code/Magento/Inventory/Model/Source/Command/GetSourcesAssignedToStockOrderedByPriority.php @@ -5,13 +5,13 @@ */ declare(strict_types=1); -namespace Magento\Inventory\Model\StockSourceLink\Command; +namespace Magento\Inventory\Model\Source\Command; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\Exception\LocalizedException; -use Magento\InventoryApi\Api\Data\SourceInterface; use Magento\InventoryApi\Api\Data\StockSourceLinkInterface; -use Magento\InventoryApi\Api\GetAssignedSourcesForStockInterface; +use Magento\InventoryApi\Api\GetSourcesAssignedToStockOrderedByPriorityInterface; use Magento\InventoryApi\Api\GetStockSourceLinksInterface; use Magento\InventoryApi\Api\SourceRepositoryInterface; use Psr\Log\LoggerInterface; @@ -19,7 +19,7 @@ /** * @inheritdoc */ -class GetAssignedSourcesForStock implements GetAssignedSourcesForStockInterface +class GetSourcesAssignedToStockOrderedByPriority implements GetSourcesAssignedToStockOrderedByPriorityInterface { /** * @var SearchCriteriaBuilder @@ -41,22 +41,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; } /** @@ -65,15 +73,13 @@ public function __construct( public function execute(int $stockId): array { try { - $sourceCodes = $this->getAssignedSourceCodes($stockId); - - $searchCriteria = $this->searchCriteriaBuilder - ->addFilter(SourceInterface::SOURCE_CODE, $sourceCodes, 'in') - ->create(); - - $searchResult = $this->sourceRepository->getList($searchCriteria); + $stockSourceLinks = $this->getStockSourceLinks($stockId); + $sources = []; + foreach ($stockSourceLinks as $link) { + $sources[] = $this->sourceRepository->get($link->getSourceCode()); + } - return $searchResult->getItems(); + return $sources; } catch (\Exception $e) { $this->logger->error($e->getMessage()); throw new LocalizedException(__('Could not load Sources for Stock'), $e); @@ -81,26 +87,23 @@ public function execute(int $stockId): array } /** - * Get all linked SourceCodes by given stockId + * Get all stock-source links by given stockId * * @param int $stockId * @return array */ - private function getAssignedSourceCodes(int $stockId): array + private function getStockSourceLinks(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); - if (0 === $searchResult->getTotalCount()) { - return []; - } - - $sourceCodes = []; - foreach ($searchResult->getItems() as $link) { - $sourceCodes[] = $link->getSourceCode(); - } - return $sourceCodes; + return $searchResult->getItems(); } } diff --git a/app/code/Magento/Inventory/etc/di.xml b/app/code/Magento/Inventory/etc/di.xml index b5b2761deebb..991fb24efdc7 100644 --- a/app/code/Magento/Inventory/etc/di.xml +++ b/app/code/Magento/Inventory/etc/di.xml @@ -11,6 +11,7 @@ + @@ -69,7 +70,6 @@ - diff --git a/app/code/Magento/InventoryApi/Api/GetAssignedSourcesForStockInterface.php b/app/code/Magento/InventoryApi/Api/GetSourcesAssignedToStockOrderedByPriorityInterface.php similarity index 77% rename from app/code/Magento/InventoryApi/Api/GetAssignedSourcesForStockInterface.php rename to app/code/Magento/InventoryApi/Api/GetSourcesAssignedToStockOrderedByPriorityInterface.php index 60ead282c846..d1a9df55d2f5 100644 --- a/app/code/Magento/InventoryApi/Api/GetAssignedSourcesForStockInterface.php +++ b/app/code/Magento/InventoryApi/Api/GetSourcesAssignedToStockOrderedByPriorityInterface.php @@ -8,16 +8,16 @@ namespace Magento\InventoryApi\Api; /** - * Get assigned Sources for Stock + * Retrieve sources related to current stock ordered by priority * * Used fully qualified namespaces in annotations for proper work of WebApi request parser * * @api */ -interface GetAssignedSourcesForStockInterface +interface GetSourcesAssignedToStockOrderedByPriorityInterface { /** - * Get Sources assigned to Stock + * Get Sources assigned to Stock ordered by priority * * If Stock with given id doesn't exist then return an empty array * diff --git a/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetAssignedSourcesForStockTest.php b/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetSourcesAssignedToStockOrderedByPriorityTest.php similarity index 90% rename from app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetAssignedSourcesForStockTest.php rename to app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetSourcesAssignedToStockOrderedByPriorityTest.php index eaf1fbcdca56..07f43b257b7d 100644 --- a/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetAssignedSourcesForStockTest.php +++ b/app/code/Magento/InventoryApi/Test/Api/StockSourceLink/GetSourcesAssignedToStockOrderedByPriorityTest.php @@ -12,13 +12,14 @@ use Magento\InventoryApi\Api\Data\SourceInterface; use Magento\TestFramework\TestCase\WebapiAbstract; -class GetAssignedSourcesForStockTest extends WebapiAbstract +class GetSourcesAssignedToStockOrderedByPriorityTest extends WebapiAbstract { /**#@+ * Service constants */ - const RESOURCE_PATH_GET_ASSIGNED_SOURCES_FOR_STOCK = '/V1/inventory/stock/get-assigned-sources'; - const SERVICE_NAME_GET_ASSIGNED_SOURCES_FOR_STOCK = 'inventoryApiGetAssignedSourcesForStockV1'; + const RESOURCE_PATH_GET_ASSIGNED_SOURCES_FOR_STOCK + = '/V1/inventory/get-sources-assigned-to-stock-ordered-by-priority'; + const SERVICE_NAME_GET_ASSIGNED_SOURCES_FOR_STOCK = 'inventoryApiGetSourcesAssignedToStockOrderedByPriorityV1'; /**#@-*/ /** @@ -28,7 +29,7 @@ class GetAssignedSourcesForStockTest extends WebapiAbstract */ public function testGetAssignedSourcesForStock() { - $stockId = 10; + $stockId = 30; $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH_GET_ASSIGNED_SOURCES_FOR_STOCK . '/' . $stockId, @@ -43,7 +44,7 @@ public function testGetAssignedSourcesForStock() ? $this->_webApiCall($serviceInfo) : $this->_webApiCall($serviceInfo, ['stockId' => $stockId]); self::assertEquals( - ['eu-1', 'eu-2', 'eu-3', 'eu-disabled'], + ['us-1', 'eu-disabled', 'eu-3', 'eu-2', 'eu-1'], array_column($response, SourceInterface::SOURCE_CODE) ); } diff --git a/app/code/Magento/InventoryApi/Test/_files/stock_source_links.php b/app/code/Magento/InventoryApi/Test/_files/stock_source_links.php index 7092502cf452..f918bffc2541 100644 --- a/app/code/Magento/InventoryApi/Test/_files/stock_source_links.php +++ b/app/code/Magento/InventoryApi/Test/_files/stock_source_links.php @@ -5,11 +5,14 @@ */ declare(strict_types=1); +use Magento\Framework\Api\DataObjectHelper; use Magento\InventoryApi\Api\Data\StockSourceLinkInterface; use Magento\InventoryApi\Api\Data\StockSourceLinkInterfaceFactory; use Magento\InventoryApi\Api\StockSourceLinksSaveInterface; use Magento\TestFramework\Helper\Bootstrap; +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); /** @var StockSourceLinksSaveInterface $stockSourceLinksSave */ $stockSourceLinksSave = Bootstrap::getObjectManager()->get(StockSourceLinksSaveInterface::class); /** @var StockSourceLinkInterfaceFactory $stockSourceLinkFactory */ @@ -25,35 +28,69 @@ * * EU-source-1(code:eu-1) - Global-stock(id:30) * EU-source-2(code:eu-2) - Global-stock(id:30) - * EU-source-2(code:eu-2) - Global-stock(id:30) + * EU-source-3(code:eu-3) - Global-stock(id:30) * EU-source-disabled(code:eu-disabled) - Global-stock(id:30) * US-source-1(code:us-1) - Global-stock(id:30) */ -/** - * $stock ID => list of source codes - */ $linksData = [ - 10 => ['eu-1', 'eu-2', 'eu-3', 'eu-disabled'], - 20 => ['us-1'], - 30 => ['eu-1', 'eu-2', 'eu-3', 'eu-disabled', 'us-1'] + [ + StockSourceLinkInterface::STOCK_ID => 10, + StockSourceLinkInterface::SOURCE_CODE => 'eu-1', + StockSourceLinkInterface::PRIORITY => 1, + ], + [ + StockSourceLinkInterface::STOCK_ID => 10, + StockSourceLinkInterface::SOURCE_CODE => 'eu-2', + StockSourceLinkInterface::PRIORITY => 2, + ], + [ + StockSourceLinkInterface::STOCK_ID => 10, + StockSourceLinkInterface::SOURCE_CODE => 'eu-3', + StockSourceLinkInterface::PRIORITY => 3, + ], + [ + StockSourceLinkInterface::STOCK_ID => 10, + StockSourceLinkInterface::SOURCE_CODE => 'eu-disabled', + StockSourceLinkInterface::PRIORITY => 4, + ], + [ + StockSourceLinkInterface::STOCK_ID => 20, + StockSourceLinkInterface::SOURCE_CODE => 'us-1', + StockSourceLinkInterface::PRIORITY => 1, + ], + [ + StockSourceLinkInterface::STOCK_ID => 30, + StockSourceLinkInterface::SOURCE_CODE => 'eu-1', + StockSourceLinkInterface::PRIORITY => 5, + ], + [ + StockSourceLinkInterface::STOCK_ID => 30, + StockSourceLinkInterface::SOURCE_CODE => 'eu-2', + StockSourceLinkInterface::PRIORITY => 4, + ], + [ + StockSourceLinkInterface::STOCK_ID => 30, + StockSourceLinkInterface::SOURCE_CODE => 'eu-3', + StockSourceLinkInterface::PRIORITY => 3, + ], + [ + StockSourceLinkInterface::STOCK_ID => 30, + StockSourceLinkInterface::SOURCE_CODE => 'eu-disabled', + StockSourceLinkInterface::PRIORITY => 2, + ], + [ + StockSourceLinkInterface::STOCK_ID => 30, + StockSourceLinkInterface::SOURCE_CODE => 'us-1', + StockSourceLinkInterface::PRIORITY => 1, + ], ]; - - $links = []; -$priority = 0; -foreach ($linksData as $stockId => $sourceCodes) { - foreach ($sourceCodes as $sourceCode) { - /** @var StockSourceLinkInterface $link */ - $link = $stockSourceLinkFactory->create(); - - $link->setStockId($stockId); - $link->setSourceCode($sourceCode); - $link->setPriority(++$priority); - - $links[] = $link; - } +foreach ($linksData as $linkData) { + /** @var StockSourceLinkInterface $link */ + $link = $stockSourceLinkFactory->create(); + $dataObjectHelper->populateWithArray($link, $linkData, StockSourceLinkInterface::class); + $links[] = $link; } - $stockSourceLinksSave->execute($links); diff --git a/app/code/Magento/InventoryApi/etc/webapi.xml b/app/code/Magento/InventoryApi/etc/webapi.xml index 49cbbf36838d..bfc74ed90f91 100644 --- a/app/code/Magento/InventoryApi/etc/webapi.xml +++ b/app/code/Magento/InventoryApi/etc/webapi.xml @@ -32,6 +32,12 @@ + + + + + + @@ -70,12 +76,6 @@ - - - - - - diff --git a/app/code/Magento/InventoryShipping/Block/Adminhtml/Order/View/Tab/SourceSelection.php b/app/code/Magento/InventoryShipping/Block/Adminhtml/Order/View/Tab/SourceSelection.php index ae253a3a269f..b2611242a2ec 100755 --- a/app/code/Magento/InventoryShipping/Block/Adminhtml/Order/View/Tab/SourceSelection.php +++ b/app/code/Magento/InventoryShipping/Block/Adminhtml/Order/View/Tab/SourceSelection.php @@ -13,6 +13,7 @@ use Magento\Framework\Registry; use Magento\InventoryApi\Api\SourceRepositoryInterface; use Magento\InventoryShipping\Model\ShippingAlgorithmProviderInterface; +use Magento\InventoryShipping\Model\ShippingAlgorithmResult\ShippingAlgorithmResultInterface; use Magento\InventoryShipping\Model\ShippingAlgorithmResult\SourceSelectionInterface; /** @@ -22,6 +23,11 @@ */ class SourceSelection extends Template implements TabInterface { + /** + * @var ShippingAlgorithmResultInterface + */ + private $shippingAlgorithmResult; + /** * @var Registry */ @@ -57,6 +63,16 @@ public function __construct( $this->sourceRepository = $sourceRepository; } + /** + * Check if order items can be shipped by the current shipping algorithm + * + * @return bool + */ + public function isShippable() + { + return $this->getShippingAlgorithmResult()->isShippable(); + } + /** * Get source selections for order * @@ -64,9 +80,7 @@ public function __construct( */ public function getSourceSelections(): array { - $order = $this->registry->registry('current_order'); - $shippingAlgorithm = $this->shippingAlgorithmProvider->execute(); - return $shippingAlgorithm->execute($order)->getSourceSelections(); + return $this->getShippingAlgorithmResult()->getSourceSelections(); } /** @@ -102,7 +116,7 @@ public function getTabTitle() */ public function canShowTab() { - return true; + return $this->isShippable(); } /** @@ -112,4 +126,19 @@ public function isHidden() { return false; } + + /** + * @return ShippingAlgorithmResultInterface + */ + private function getShippingAlgorithmResult() + { + if (null === $this->shippingAlgorithmResult) { + $order = $this->registry->registry('current_order'); + $shippingAlgorithm = $this->shippingAlgorithmProvider->execute(); + + $this->shippingAlgorithmResult = $shippingAlgorithm->execute($order); + } + + return $this->shippingAlgorithmResult; + } } diff --git a/app/code/Magento/InventoryShipping/Model/DefaultShippingAlgorithm.php b/app/code/Magento/InventoryShipping/Model/DefaultShippingAlgorithm.php deleted file mode 100644 index a3263f803ac6..000000000000 --- a/app/code/Magento/InventoryShipping/Model/DefaultShippingAlgorithm.php +++ /dev/null @@ -1,134 +0,0 @@ -getSourceItemsBySku = $getSourceItemsBySku; - $this->shippingAlgorithmResultFactory = $shippingAlgorithmResultFactory; - $this->sourceSelectionFactory = $sourceSelectionFactory; - $this->sourceItemSelectionFactory = $sourceItemSelectionFactory; - } - - /** - * @inheritdoc - */ - public function execute(OrderInterface $order): ShippingAlgorithmResultInterface - { - $this->isShippable = true; - $sourceItemSelectionsData = $this->getSourceItemSelectionsData($order); - - $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 = []; - - foreach ($order->getItems() as $orderItem) { - if ($orderItem->isDeleted() || $orderItem->getParentItemId()) { - continue; - } - - $itemSku = $orderItem->getSku(); - $sourceItems = $this->getSourceItemsBySku->execute($itemSku)->getItems(); - - $qtyToDeliver = $orderItem->getQtyOrdered(); - foreach ($sourceItems as $sourceItem) { - if ($qtyToDeliver < 0.0001) { - break; - } - - $sourceItemQty = $sourceItem->getQuantity(); - if ($sourceItemQty > 0) { - $qtyToDeduct = min($sourceItemQty, $qtyToDeliver); - - $sourceItemSelections[$sourceItem->getSourceCode()][] = $this->sourceItemSelectionFactory->create([ - 'sku' => $itemSku, - 'qty' => $qtyToDeduct, - 'qtyAvailable' => $sourceItemQty, - ]); - - $qtyToDeliver -= $qtyToDeduct; - } - } - - if ($qtyToDeliver > 0.0001) { - $this->isShippable = false; - } - } - - return $sourceItemSelections; - } -} diff --git a/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetEnabledSourcesOrderedByPriorityByStoreId.php b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetEnabledSourcesOrderedByPriorityByStoreId.php new file mode 100644 index 000000000000..c5810bcb794f --- /dev/null +++ b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetEnabledSourcesOrderedByPriorityByStoreId.php @@ -0,0 +1,76 @@ +storeManager = $storeManager; + $this->websiteRepository = $websiteRepository; + $this->stockResolver = $stockResolver; + $this->getSourcesAssignedToStockOrderedByPriority = $getSourcesAssignedToStockOrderedByPriority; + } + + /** + * @param int $storeId + * @return SourceInterface[] + */ + public function execute(int $storeId): array + { + $store = $this->storeManager->getStore($storeId); + $website = $this->websiteRepository->getById($store->getWebsiteId()); + $stock = $this->stockResolver->get(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); + + $sources = $this->getSourcesAssignedToStockOrderedByPriority->execute((int)$stock->getStockId()); + $sources = array_filter($sources, function (SourceInterface $source) { + return $source->isEnabled(); + }); + return $sources; + } +} diff --git a/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetSourceItemBySourceCodeAndSku.php b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetSourceItemBySourceCodeAndSku.php new file mode 100644 index 000000000000..f78bc677e2bc --- /dev/null +++ b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/GetSourceItemBySourceCodeAndSku.php @@ -0,0 +1,58 @@ +sourceItemRepository = $sourceItemRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * Returns source item from specific source by given SKU. Return null if source item is not found + * + * @param string $sourceCode + * @param string $sku + * @return SourceItemInterface|null + */ + public function execute(string $sourceCode, string $sku) + { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter(SourceItemInterface::SOURCE_CODE, $sourceCode) + ->addFilter(SourceItemInterface::SKU, $sku) + ->create(); + $sourceItemsResult = $this->sourceItemRepository->getList($searchCriteria); + + return $sourceItemsResult->getTotalCount() > 0 ? current($sourceItemsResult->getItems()) : null; + } +} diff --git a/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/PriorityShippingAlgorithm.php b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/PriorityShippingAlgorithm.php new file mode 100644 index 000000000000..77b49fb8a9a1 --- /dev/null +++ b/app/code/Magento/InventoryShipping/Model/PriorityShippingAlgorithm/PriorityShippingAlgorithm.php @@ -0,0 +1,188 @@ +shippingAlgorithmResultFactory = $shippingAlgorithmResultFactory; + $this->sourceSelectionFactory = $sourceSelectionFactory; + $this->sourceItemSelectionFactory = $sourceItemSelectionFactory; + $this->getSourceItemBySourceCodeAndSku = $getSourceItemBySourceCodeAndSku; + $this->getEnabledSourcesOrderedByPriorityByStoreId = $getEnabledSourcesOrderedByPriorityByStoreId; + } + + /** + * @inheritdoc + */ + public function execute(OrderInterface $order): ShippingAlgorithmResultInterface + { + $isShippable = true; + $storeId = $order->getStoreId(); + $sources = $this->getEnabledSourcesOrderedByPriorityByStoreId->execute((int)$storeId); + $sourceItemSelections = []; + + /** @var OrderItemInterface|OrderItem $orderItem */ + foreach ($order->getItems() as $orderItem) { + $itemSku = $orderItem->getSku(); + $qtyToDeliver = $orderItem->getQtyOrdered(); + + //check if order item is not delivered yet + if ($orderItem->isDeleted() || $orderItem->getParentItemId() || $this->isZero((float)$qtyToDeliver)) { + continue; + } + + foreach ($sources as $source) { + $sourceItem = $this->getSourceItemBySourceCodeAndSku->execute($source->getSourceCode(), $itemSku); + if (null === $sourceItem) { + continue; + } + + $sourceItemQty = $sourceItem->getQuantity(); + $qtyToDeduct = min($sourceItemQty, $qtyToDeliver); + + // check if source has some qty of SKU, so it's possible to take them into account + if ($this->isZero((float)$sourceItemQty)) { + continue; + } + + $sourceItemSelection = $this->sourceItemSelectionFactory->create( + [ + 'sku' => $itemSku, + 'qty' => $qtyToDeduct, + 'qtyAvailable' => $sourceItemQty, + ] + ); + + $sourceItemSelections = $this->updateSourceItemSelections( + $sourceItemSelections, + $sourceItemSelection, + $sourceItem + ); + + $qtyToDeliver -= $qtyToDeduct; + } + + // if we go throw all sources from the stock and there is still some qty to delivery, + // then it doesn't have enough items to delivery + if (!$this->isZero($qtyToDeliver)) { + $isShippable = false; + } + } + + $sourceSelections = $this->createSourceSelection($sourceItemSelections); + + return $this->shippingAlgorithmResultFactory->create([ + 'sourceSelections' => $sourceSelections, + 'isShippable' => $isShippable + ]); + } + + /** + * Compare float number with some epsilon + * + * @param float $floatNumber + * + * @return bool + */ + private function isZero(float $floatNumber): bool + { + return $floatNumber < 0.0000001; + } + + /** + * @param SourceItemSelectionInterface[] $sourceItemSelections + * @return SourceSelectionInterface[] + */ + private function createSourceSelection(array $sourceItemSelections): array + { + $sourceSelections = []; + foreach ($sourceItemSelections as $sourceCode => $items) { + $sourceSelections[] = $this->sourceSelectionFactory->create( + [ + 'sourceCode' => $sourceCode, + 'sourceItemSelections' => $items + ] + ); + } + return $sourceSelections; + } + + /** + * @param SourceItemSelectionInterface[] $sourceItemSelections + * @param SourceItemSelectionInterface $sourceItemSelection + * @param SourceItemInterface $sourceItem + * @return SourceItemSelectionInterface[] + */ + private function updateSourceItemSelections( + array $sourceItemSelections, + SourceItemSelectionInterface $sourceItemSelection, + SourceItemInterface $sourceItem + ): array { + if (isset($sourceItemSelections[$sourceItem->getSourceCode()])) { + $sourceItemSelections[$sourceItem->getSourceCode()][] = $sourceItemSelection; + } else { + $sourceItemSelections[$sourceItem->getSourceCode()] = [$sourceItemSelection]; + } + return $sourceItemSelections; + } +} diff --git a/app/code/Magento/InventoryShipping/Model/ShippingAlgorithmProvider.php b/app/code/Magento/InventoryShipping/Model/ShippingAlgorithmProvider.php index 436ec7b21519..c44eb945afe0 100644 --- a/app/code/Magento/InventoryShipping/Model/ShippingAlgorithmProvider.php +++ b/app/code/Magento/InventoryShipping/Model/ShippingAlgorithmProvider.php @@ -8,6 +8,7 @@ namespace Magento\InventoryShipping\Model; use Magento\Framework\ObjectManagerInterface; +use Magento\InventoryShipping\Model\PriorityShippingAlgorithm\PriorityShippingAlgorithm; /** * @inheritdoc @@ -33,6 +34,6 @@ public function __construct( */ public function execute(): ShippingAlgorithmInterface { - return $this->objectManager->get(DefaultShippingAlgorithm::class); + return $this->objectManager->get(PriorityShippingAlgorithm::class); } } diff --git a/app/code/Magento/InventoryShipping/Test/Integration/DefaultShippingAlgorithmTest.php b/app/code/Magento/InventoryShipping/Test/Integration/PriorityShippingAlgorithmTest.php similarity index 64% rename from app/code/Magento/InventoryShipping/Test/Integration/DefaultShippingAlgorithmTest.php rename to app/code/Magento/InventoryShipping/Test/Integration/PriorityShippingAlgorithmTest.php index 89ab1b4f490e..b60ffe1f6cde 100644 --- a/app/code/Magento/InventoryShipping/Test/Integration/DefaultShippingAlgorithmTest.php +++ b/app/code/Magento/InventoryShipping/Test/Integration/PriorityShippingAlgorithmTest.php @@ -8,14 +8,15 @@ namespace Magento\InventoryShipping\Test\Integration; use Magento\InventoryCatalog\Api\DefaultSourceProviderInterface; -use Magento\InventoryShipping\Model\DefaultShippingAlgorithm; +use Magento\InventoryShipping\Model\PriorityShippingAlgorithm\PriorityShippingAlgorithm; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderInterfaceFactory; use Magento\Sales\Api\Data\OrderItemInterfaceFactory; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; -class DefaultShippingAlgorithmTest extends TestCase +class PriorityShippingAlgorithmTest extends TestCase { /** * @var DefaultSourceProviderInterface @@ -33,16 +34,25 @@ class DefaultShippingAlgorithmTest extends TestCase private $orderFactory; /** - * @var DefaultShippingAlgorithm + * @var PriorityShippingAlgorithm */ private $shippingAlgorithm; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @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); + $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); } /** @@ -83,53 +93,91 @@ public function testDefaultStockSource() } /** - * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php - * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php - * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php - * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php - * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_links.php + * @return array */ - public function testStockSourceCombination() + public function stockSourceCombinationDataProvider(): array { - $expectedResult = [ + return [ [ - 'source_code' => 'eu-1', - 'source_item_selections' => [ - ['SKU-1', 5.5, 5.5], + 'store_code' => 'store_for_eu_website', + 'order_data' => [ + 'SKU-1' => 14.5, + 'SKU-3' => 3, ], - ], - [ - 'source_code' => 'eu-2', - 'source_item_selections' => [ - ['SKU-1', 3, 3], - ['SKU-3', 3, 6], + 'expected_result' => [ + [ + 'source_code' => 'eu-1', + 'source_item_selections' => [ + ['SKU-1', 5.5, 5.5], + ], + ], + [ + 'source_code' => 'eu-2', + 'source_item_selections' => [ + ['SKU-1', 3, 3], + ['SKU-3', 3, 6], + ], + ], + [ + 'source_code' => 'eu-3', + 'source_item_selections' => [ + ['SKU-1', 6, 10], + ], + ], ], ], [ - 'source_code' => 'eu-3', - 'source_item_selections' => [ - ['SKU-1', 10, 10], + 'store_code' => 'store_for_global_website', + 'order_data' => [ + 'SKU-1' => 14.5, + 'SKU-3' => 3, ], - ], - [ - 'source_code' => 'eu-disabled', - 'source_item_selections' => [ - ['SKU-1', 9, 10], - ], - ], - [ - 'source_code' => 'us-1', - 'source_item_selections' => [ - ['SKU-2', 4.5, 5], + 'expected_result' => [ + [ + 'source_code' => 'eu-3', + 'source_item_selections' => [ + ['SKU-1', 10, 10], + ], + ], + [ + 'source_code' => 'eu-2', + 'source_item_selections' => [ + ['SKU-1', 3, 3], + ['SKU-3', 3, 6], + ], + ], + [ + 'source_code' => 'eu-1', + 'source_item_selections' => [ + ['SKU-1', 1.5, 5.5], + ], + ], ], ], ]; + } - $order = $this->createOrder([ - 'SKU-1' => 27.5, - 'SKU-2' => 4.5, - 'SKU-3' => 3, - ]); + /** + * Tests source selections with different source-stock link priorities. + * + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_links.php + * @magentoDataFixture ../../../../app/code/Magento/InventorySalesApi/Test/_files/websites_with_stores.php + * @magentoDataFixture ../../../../app/code/Magento/InventorySalesApi/Test/_files/stock_website_sales_channels.php + * @dataProvider stockSourceCombinationDataProvider + * @param string $storeCode + * @param array $orderData + * @param array $expectedResult + */ + public function testStockSourceCombination(string $storeCode, array $orderData, array $expectedResult) + { + $order = $this->createOrder( + $orderData, + $storeCode + ); $algorithmResult = $this->shippingAlgorithm->execute($order); $sourceSelections = $algorithmResult->getSourceSelections(); @@ -173,9 +221,10 @@ public function testStockSourceCombinationNoShippable() * Returns order object with specified products * * @param array $productsQty + * @param string $storeCode * @return OrderInterface */ - private function createOrder(array $productsQty): OrderInterface + private function createOrder(array $productsQty, string $storeCode = 'default'): OrderInterface { $orderItems = []; foreach ($productsQty as $sku => $qty) { @@ -189,6 +238,8 @@ private function createOrder(array $productsQty): OrderInterface /** @var OrderInterface $order */ $order = Bootstrap::getObjectManager()->create(OrderInterface::class); + $storeId = $this->storeManager->getStore($storeCode)->getId(); + $order->setStoreId($storeId); $order->setItems($orderItems); return $order; } diff --git a/app/code/Magento/InventoryShipping/composer.json b/app/code/Magento/InventoryShipping/composer.json index 9dd23397c158..29b7e641d5e3 100644 --- a/app/code/Magento/InventoryShipping/composer.json +++ b/app/code/Magento/InventoryShipping/composer.json @@ -6,8 +6,9 @@ "magento/framework": "100.3.*", "magento/module-backend": "100.0.*", "magento/module-inventory-api": "100.0.*", - "magento/module-inventory": "100.0.*", - "magento/module-sales": "100.3.*" + "magento/module-inventory-sales-api": "100.0.*", + "magento/module-sales": "100.3.*", + "magento/module-store": "100.3.*" }, "type": "magento2-module", "version": "100.0.0-dev", diff --git a/app/code/Magento/InventoryShipping/etc/module.xml b/app/code/Magento/InventoryShipping/etc/module.xml index 3ee6211763d9..3047f2c7cf18 100644 --- a/app/code/Magento/InventoryShipping/etc/module.xml +++ b/app/code/Magento/InventoryShipping/etc/module.xml @@ -6,9 +6,5 @@ */ --> - - - - - + diff --git a/app/code/Magento/InventoryShipping/view/adminhtml/templates/order/view/tab/source_selection.phtml b/app/code/Magento/InventoryShipping/view/adminhtml/templates/order/view/tab/source_selection.phtml index 3cdd787153b4..256661af6fdd 100644 --- a/app/code/Magento/InventoryShipping/view/adminhtml/templates/order/view/tab/source_selection.phtml +++ b/app/code/Magento/InventoryShipping/view/adminhtml/templates/order/view/tab/source_selection.phtml @@ -4,14 +4,14 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - -/** @var \Magento\InventoryShipping\Block\Adminhtml\Order\View\Tab\SourceSelection $block */ +/** @var Magento\InventoryShipping\Block\Adminhtml\Order\View\Tab\SourceSelection $block */ ?> -getSourceSelections() as $sourceSelection): ?> +getSourceSelections() as $sourceSelection) : ?>
- escapeHtml($this->getSourceName($sourceSelection->getSourceCode())) ?> + + escapeHtml($this->getSourceName($sourceSelection->getSourceCode())) ?> +
@@ -20,7 +20,7 @@ - getSourceItemSelections() as $itemSelection): ?> + getSourceItemSelections() as $itemSelection) : ?>
escapeHtml(__('Qty to Deduct')) ?>
escapeHtml($itemSelection->getSku()) ?> escapeHtml($itemSelection->getQtyAvailable()) ?>