Skip to content

Commit bb6fdac

Browse files
author
Valeriy Nayda
authored
Merge pull request magento#156 from magento-engcom/inventory-sales-backorder
Inventory sales backorder functionality
2 parents 39320a3 + a423acd commit bb6fdac

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

app/code/Magento/InventoryApi/Test/_files/products.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
$productFactory = $objectManager->get(ProductInterfaceFactory::class);
1515
/** @var ProductRepositoryInterface $productRepository */
1616
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
17+
$productRepository->cleanCache();
1718

1819
for ($i = 1; $i <= 3; $i++) {
1920
$product = $productFactory->create();
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\InventorySales\Plugin\InventoryApi;
9+
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
12+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
14+
use Magento\InventoryApi\Api\IsProductInStockInterface;
15+
16+
/**
17+
* Adapt backorders to IsProductInStockInterface
18+
*/
19+
class BackorderStockStatusPlugin
20+
{
21+
/**
22+
* @var StockItemRepositoryInterface
23+
*/
24+
private $stockItemRepository;
25+
26+
/**
27+
* @var StockItemCriteriaInterfaceFactory
28+
*/
29+
private $stockItemCriteriaFactory;
30+
31+
/**
32+
* @var ProductRepositoryInterface
33+
*/
34+
private $productRepository;
35+
36+
/**
37+
* @param StockItemRepositoryInterface $stockItemRepository
38+
* @param StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory
39+
* @param ProductRepositoryInterface $productRepository
40+
*/
41+
public function __construct(
42+
StockItemRepositoryInterface $stockItemRepository,
43+
StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory,
44+
ProductRepositoryInterface $productRepository
45+
) {
46+
$this->stockItemRepository = $stockItemRepository;
47+
$this->stockItemCriteriaFactory = $stockItemCriteriaFactory;
48+
$this->productRepository = $productRepository;
49+
}
50+
51+
/**
52+
* Return true status if backorders is enabled for the item
53+
*
54+
* @param IsProductInStockInterface $subject
55+
* @param callable $proceed
56+
* @param string $sku
57+
* @param int $stockId
58+
* @return bool
59+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
60+
*/
61+
public function aroundExecute(
62+
IsProductInStockInterface $subject,
63+
callable $proceed,
64+
string $sku,
65+
int $stockId
66+
): bool {
67+
$productData = $this->productRepository->get($sku);
68+
$productId = $productData->getId();
69+
70+
$stockItemCriteria = $this->stockItemCriteriaFactory->create();
71+
$stockItemCriteria->setProductsFilter($productId);
72+
$stockItemsCollection = $this->stockItemRepository->getList($stockItemCriteria);
73+
74+
/** @var StockItemInterface $legacyStockItem */
75+
$legacyStockItem = current($stockItemsCollection->getItems());
76+
77+
if ($legacyStockItem->getBackorders() > 0) {
78+
return true;
79+
}
80+
return $proceed($sku, $stockId);
81+
}
82+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\InventorySales\Test\Integration;
7+
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
10+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
11+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
12+
use Magento\Framework\Api\SearchCriteriaBuilder;
13+
use Magento\Framework\Indexer\IndexerInterface;
14+
use Magento\Inventory\Indexer\StockItemIndexerInterface;
15+
use Magento\InventoryApi\Api\Data\SourceItemInterface;
16+
use Magento\InventoryApi\Api\IsProductInStockInterface;
17+
use Magento\InventoryApi\Api\SourceItemRepositoryInterface;
18+
use Magento\InventoryApi\Api\SourceItemsSaveInterface;
19+
use Magento\TestFramework\Helper\Bootstrap;
20+
use PHPUnit\Framework\TestCase;
21+
22+
class IsBackorderedProductInStockTest extends TestCase
23+
{
24+
/**
25+
* @var SourceItemRepositoryInterface
26+
*/
27+
private $sourceItemRepository;
28+
29+
/**
30+
* @var SearchCriteriaBuilder
31+
*/
32+
private $searchCriteriaBuilder;
33+
34+
/**
35+
* @var SourceItemsSaveInterface
36+
*/
37+
private $sourceItemsSave;
38+
39+
/**
40+
* @var IndexerInterface
41+
*/
42+
private $indexer;
43+
44+
/**
45+
* @var ProductRepositoryInterface
46+
*/
47+
private $productRepository;
48+
49+
/**
50+
* @var IsProductInStockInterface
51+
*/
52+
private $isProductInStock;
53+
54+
/**
55+
* @var StockItemRepositoryInterface
56+
*/
57+
private $stockItemRepository;
58+
59+
/**
60+
* @var StockItemCriteriaInterfaceFactory
61+
*/
62+
private $stockItemCriteriaFactory;
63+
64+
protected function setUp()
65+
{
66+
$this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class);
67+
$this->stockItemRepository = Bootstrap::getObjectManager()->create(StockItemRepositoryInterface::class);
68+
$this->stockItemCriteriaFactory = Bootstrap::getObjectManager()->create(
69+
StockItemCriteriaInterfaceFactory::class
70+
);
71+
$this->sourceItemRepository = Bootstrap::getObjectManager()->create(SourceItemRepositoryInterface::class);
72+
$this->searchCriteriaBuilder = Bootstrap::getObjectManager()->create(SearchCriteriaBuilder::class);
73+
$this->sourceItemsSave = Bootstrap::getObjectManager()->create(SourceItemsSaveInterface::class);
74+
$this->indexer = Bootstrap::getObjectManager()->create(IndexerInterface::class);
75+
$this->indexer->load(StockItemIndexerInterface::INDEXER_ID);
76+
$this->isProductInStock = Bootstrap::getObjectManager()->create(IsProductInStockInterface::class);
77+
}
78+
79+
/**
80+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php
81+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php
82+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php
83+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php
84+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_link.php
85+
*/
86+
public function testBackorderedZeroQtyProductIsInStock()
87+
{
88+
$product = $this->productRepository->get('SKU-2');
89+
$stockItemSearchCriteria = $this->stockItemCriteriaFactory->create();
90+
$stockItemSearchCriteria->setProductsFilter($product->getId());
91+
$stockItemsCollection = $this->stockItemRepository->getList($stockItemSearchCriteria);
92+
93+
/** @var StockItemInterface $legacyStockItem */
94+
$legacyStockItem = current($stockItemsCollection->getItems());
95+
$legacyStockItem->setBackorders(1);
96+
$legacyStockItem->setUseConfigBackorders(0);
97+
$this->stockItemRepository->save($legacyStockItem);
98+
99+
$sourceItem = $this->getSourceItemBySku('SKU-2');
100+
$this->changeSourceItemQty($sourceItem, -15);
101+
102+
$this->assertTrue($this->isProductInStock->execute('SKU-2', 1));
103+
}
104+
105+
/**
106+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php
107+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php
108+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php
109+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php
110+
* @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_link.php
111+
*/
112+
public function testZeroQtyProductIsOutOfStock()
113+
{
114+
$sourceItem = $this->getSourceItemBySku('SKU-2');
115+
$this->changeSourceItemQty($sourceItem, 0);
116+
117+
$this->assertFalse($this->isProductInStock->execute('SKU-2', 1));
118+
}
119+
120+
/**
121+
* @param string $sku
122+
* @return SourceItemInterface
123+
*/
124+
private function getSourceItemBySku(string $sku): SourceItemInterface
125+
{
126+
$sourceItemSearchCriteria = $this->searchCriteriaBuilder
127+
->addFilter('sku', $sku)
128+
->create();
129+
$sourceItemSearchResult = $this->sourceItemRepository->getList($sourceItemSearchCriteria);
130+
131+
return current($sourceItemSearchResult->getItems());
132+
}
133+
134+
/**
135+
* @param SourceItemInterface $sourceItem
136+
* @param float $qty
137+
*/
138+
private function changeSourceItemQty(SourceItemInterface $sourceItem, float $qty)
139+
{
140+
$sourceItem->setQuantity($qty);
141+
$this->sourceItemsSave->execute([$sourceItem]);
142+
$this->indexer->reindexRow(5);
143+
}
144+
}

app/code/Magento/InventorySales/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@
1919
<type name="Magento\Inventory\Ui\DataProvider\StockDataProvider">
2020
<plugin name="sales_channel_data" type="Magento\InventorySales\Plugin\Inventory\Ui\StockDataProvider\SalesChannels" />
2121
</type>
22+
<type name="Magento\InventoryApi\Api\IsProductInStockInterface">
23+
<plugin name="backorder_inventory_stock_status" type="Magento\InventorySales\Plugin\InventoryApi\BackorderStockStatusPlugin"/>
24+
</type>
2225
</config>

0 commit comments

Comments
 (0)