diff --git a/InventoryDataExporter/Model/Provider/StockStatus.php b/InventoryDataExporter/Model/Provider/StockStatus.php
index fb9917cf..ad406776 100644
--- a/InventoryDataExporter/Model/Provider/StockStatus.php
+++ b/InventoryDataExporter/Model/Provider/StockStatus.php
@@ -80,18 +80,16 @@ public function get(array $values): array
$output = [];
try {
- $processedSkus = [];
$select = $this->query->getQuery($skus);
// $select can be null if no stocks exists except default
if ($select) {
$cursor = $connection->query($select);
while ($row = $cursor->fetch()) {
- $processedSkus[] = $row['sku'];
$output[] = $this->fillWithDefaultValues($row);
}
}
- $select = $this->query->getQueryForDefaultStock(\array_diff($skus, $processedSkus));
+ $select = $this->query->getQueryForDefaultStock($skus);
$cursor = $connection->query($select);
while ($row = $cursor->fetch()) {
$output[] = $this->fillWithDefaultValues($row);
diff --git a/InventoryDataExporter/Model/Query/InventoryStockQuery.php b/InventoryDataExporter/Model/Query/InventoryStockQuery.php
index 19a9cafd..9e808b8f 100644
--- a/InventoryDataExporter/Model/Query/InventoryStockQuery.php
+++ b/InventoryDataExporter/Model/Query/InventoryStockQuery.php
@@ -21,13 +21,17 @@ class InventoryStockQuery
* @var ResourceConnection
*/
private $resourceConnection;
+
/**
* @var DefaultStockProviderInterface
*/
private $defaultStockProvider;
+ private const DEFAULT_STOCK_SOURCE = 'default';
+
/**
* @param ResourceConnection $resourceConnection
+ * @param DefaultStockProviderInterface $defaultStockProvider
*/
public function __construct(
ResourceConnection $resourceConnection,
@@ -131,6 +135,15 @@ public function getQueryForDefaultStock(array $skus): Select
],
'stock_item.product_id = isi.product_id',
[]
+ )->joinInner(
+ [
+ 'source_item' => 'inventory_source_item'
+ ],
+ $connection->quoteInto(
+ 'source_item.source_code = ? and source_item.sku = isi.sku',
+ self::DEFAULT_STOCK_SOURCE
+ ),
+ []
)->columns(
[
'qty' => "isi.quantity",
diff --git a/InventoryDataExporter/Model/Query/StockStatusDeleteQuery.php b/InventoryDataExporter/Model/Query/StockStatusDeleteQuery.php
index 06b79551..1b7f1734 100644
--- a/InventoryDataExporter/Model/Query/StockStatusDeleteQuery.php
+++ b/InventoryDataExporter/Model/Query/StockStatusDeleteQuery.php
@@ -9,6 +9,8 @@
use Magento\DataExporter\Model\Indexer\FeedIndexMetadata;
use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\Serialize\SerializerInterface;
+use Magento\Framework\Stdlib\DateTime;
/**
* Stock Status mark as deleted query builder
@@ -25,16 +27,32 @@ class StockStatusDeleteQuery
*/
private $metadata;
+ /**
+ * @var SerializerInterface
+ */
+ private $serializer;
+
+ /**
+ * @var DateTime
+ */
+ private $dateTime;
+
/**
* @param ResourceConnection $resourceConnection
* @param FeedIndexMetadata $metadata
+ * @param SerializerInterface $serializer
+ * @param DateTime $dateTime
*/
public function __construct(
ResourceConnection $resourceConnection,
- FeedIndexMetadata $metadata
+ FeedIndexMetadata $metadata,
+ SerializerInterface $serializer,
+ DateTime $dateTime
) {
$this->resourceConnection = $resourceConnection;
$this->metadata = $metadata;
+ $this->serializer = $serializer;
+ $this->dateTime = $dateTime;
}
/**
@@ -49,10 +67,11 @@ public function getStocksAssignedToSkus(array $skus): array
$select = $connection->select()
->from(
['source_item' => $this->resourceConnection->getTableName('inventory_source_item')],
- ['source_item.sku', 'source_stock_link.stock_id']
+ ['source_item.sku', 'source_stock_link.stock_id', 'source_stock_link.source_code']
)->joinLeft(
['source_stock_link' => $this->resourceConnection->getTableName('inventory_source_stock_link')],
- 'source_item.source_code = source_stock_link.source_code'
+ 'source_item.source_code = source_stock_link.source_code',
+ []
)->where('source_item.sku IN (?)', $skus);
$fetchedSourceItems = [];
@@ -63,6 +82,39 @@ public function getStocksAssignedToSkus(array $skus): array
return $fetchedSourceItems;
}
+ /**
+ * Get stocks which are assigned to the list of provided SKUs
+ *
+ * @param array $sourceCodes
+ * @return array
+ */
+ public function getStocksWithSources(array $sourceCodes): array
+ {
+ $connection = $this->resourceConnection->getConnection();
+ $sourceLinkTableName = $this->resourceConnection->getTableName('inventory_source_stock_link');
+ $select = $connection->select()
+ ->from(
+ ['source_stock_link' => $sourceLinkTableName],
+ ['source_stock_link.stock_id', 'source_stock_link_all_sources.source_code']
+ )->joinInner(
+ ['source_stock_link_all_sources' => $sourceLinkTableName],
+ 'source_stock_link_all_sources.stock_id = source_stock_link.stock_id',
+ []
+ )->where(
+ 'source_stock_link.source_code IN (?)',
+ $sourceCodes
+ )->group(
+ ['source_stock_link.stock_id',
+ 'source_stock_link_all_sources.source_code'
+ ]
+ );
+ $stocks = [];
+ foreach ($connection->fetchAll($select) as $stockData) {
+ $stocks[$stockData['stock_id']][] = $stockData['source_code'];
+ }
+ return $stocks;
+ }
+
/**
* Mark stock statuses as deleted
*
@@ -70,14 +122,52 @@ public function getStocksAssignedToSkus(array $skus): array
*/
public function markStockStatusesAsDeleted(array $idsToDelete): void
{
+ $records = [];
+ foreach ($idsToDelete as $deletedItemId => $stockStatusData) {
+ $records[] = $this->buildFeedData($deletedItemId, $stockStatusData);
+ }
$connection = $this->resourceConnection->getConnection();
$feedTableName = $this->resourceConnection->getTableName($this->metadata->getFeedTableName());
- $connection->update(
+ $connection->insertOnDuplicate(
$feedTableName,
- ['is_deleted' => new \Zend_Db_Expr('1')],
- [
- 'id IN (?)' => $idsToDelete
- ]
+ $records
);
}
+
+ /**
+ * @param string $stockStatusId
+ * @param array $stockIdAndSku
+ * @return array
+ */
+ private function buildFeedData(string $stockStatusId, array $stockIdAndSku): array
+ {
+ if (!isset($stockIdAndSku['stock_id'], $stockIdAndSku['sku'])) {
+ throw new \RuntimeException(
+ sprintf(
+ "inventory_data_exporter_stock_status indexer error: cannot build unique id from %s",
+ \var_export($stockIdAndSku, true)
+ )
+ );
+ }
+ $feedData = [
+ 'id' => $stockStatusId,
+ 'stockId' => $stockIdAndSku['stock_id'],
+ 'sku' => $stockIdAndSku['sku'],
+ 'qty' => 0,
+ 'qtyForSale' => 0,
+ 'infiniteStock' => false,
+ 'isSalable' => false,
+ 'updatedAt' => $this->dateTime->formatDate(time())
+
+ ];
+
+ return [
+ 'id' => $stockStatusId,
+ 'stock_id' => $stockIdAndSku['stock_id'],
+ 'sku' => $stockIdAndSku['sku'],
+ 'feed_data' => $this->serializer->serialize($feedData),
+ 'is_deleted' => 1,
+ 'modified_at' => $this->dateTime->formatDate(time())
+ ];
+ }
}
diff --git a/InventoryDataExporter/Plugin/BulkSourceUnassign.php b/InventoryDataExporter/Plugin/BulkSourceUnassign.php
index 5d9e1802..30870041 100644
--- a/InventoryDataExporter/Plugin/BulkSourceUnassign.php
+++ b/InventoryDataExporter/Plugin/BulkSourceUnassign.php
@@ -5,6 +5,7 @@
*/
namespace Magento\InventoryDataExporter\Plugin;
+use Magento\InventoryCatalogApi\Api\BulkSourceUnassignInterface;
use Magento\InventoryDataExporter\Model\Provider\StockStatusIdBuilder;
use Magento\InventoryDataExporter\Model\Query\StockStatusDeleteQuery;
@@ -30,49 +31,69 @@ public function __construct(
/**
* Check which stocks will be unassigned from products and mark them as deleted in feed table
*
- * @param \Magento\InventoryCatalog\Model\ResourceModel\BulkSourceUnassign $subject
+ * * @param BulkSourceUnassignInterface $subject
+ * @param int $result
* @param array $skus
* @param array $sourceCodes
- * @return void
+ * @return int
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function beforeExecute(
- \Magento\InventoryCatalog\Model\ResourceModel\BulkSourceUnassign $subject,
+ public function afterExecute(
+ BulkSourceUnassignInterface $subject,
+ int $result,
array $skus,
array $sourceCodes
- ): void {
- $fetchedSourceItems = $this->stockStatusDeleteQuery->getStocksAssignedToSkus($skus);
- $stocksToDelete = $this->getStocksToDelete($skus, $sourceCodes, $fetchedSourceItems);
+ ): int {
+ $sourcesAssignedToProducts = $this->stockStatusDeleteQuery->getStocksAssignedToSkus($skus);
+ $sourcesByStocks = $this->stockStatusDeleteQuery->getStocksWithSources($sourceCodes);
+ $stocksToDelete = $this->getStocksToDelete($skus, $sourcesByStocks, $sourcesAssignedToProducts);
if (!empty($stocksToDelete)) {
$this->stockStatusDeleteQuery->markStockStatusesAsDeleted($stocksToDelete);
}
+
+ return $result;
}
/**
* @param array $affectedSkus
- * @param array $deletedSources
- * @param $fetchedSourceItems
+ * @param array $sourcesByStocks
+ * @param array $sourcesAssignedToProducts
* @return array
*/
- private function getStocksToDelete(array $affectedSkus, array $deletedSources, array $fetchedSourceItems): array
- {
+ private function getStocksToDelete(
+ array $affectedSkus,
+ array $sourcesByStocks,
+ array $sourcesAssignedToProducts
+ ): array {
$stocksToDelete = [];
foreach ($affectedSkus as $deletedItemSku) {
- if (!isset($fetchedSourceItems[$deletedItemSku])) {
+ foreach (array_keys($sourcesByStocks) as $stockId) {
+ $stockStatusId = StockStatusIdBuilder::build(
+ ['stockId' => (string)$stockId, 'sku' => $deletedItemSku]
+ );
+ $stocksToDelete[$stockStatusId] = [
+ 'stock_id' => (string)$stockId,
+ 'sku' => $deletedItemSku
+ ];
+ }
+ if (!isset($sourcesAssignedToProducts[$deletedItemSku])) {
continue ;
}
- foreach ($fetchedSourceItems[$deletedItemSku] as $fetchedItemStockId => $fetchedItemSources) {
- if ($this->getContainsAllKeys($fetchedItemSources, $deletedSources)) {
- $stocksToDelete[] = StockStatusIdBuilder::build(
+
+ foreach ($sourcesAssignedToProducts[$deletedItemSku] as $fetchedItemStockId => $fetchedItemSources) {
+ if (isset($sourcesByStocks[$fetchedItemStockId])
+ && $this->getContainsAllKeys($fetchedItemSources, $sourcesByStocks[$fetchedItemStockId])) {
+ $stockStatusId = StockStatusIdBuilder::build(
['stockId' => (string)$fetchedItemStockId, 'sku' => $deletedItemSku]
);
+ unset($stocksToDelete[$stockStatusId]);
}
}
}
- return $stocksToDelete;
+ return array_filter($stocksToDelete);
}
/**
diff --git a/InventoryDataExporter/Plugin/MarkItemsAsDeleted.php b/InventoryDataExporter/Plugin/MarkItemsAsDeleted.php
index 0e883038..e1f4b068 100644
--- a/InventoryDataExporter/Plugin/MarkItemsAsDeleted.php
+++ b/InventoryDataExporter/Plugin/MarkItemsAsDeleted.php
@@ -66,9 +66,13 @@ private function getStocksToDelete(array $deletedSourceItems, $fetchedSourceItem
foreach ($deletedSourceItems as $deletedItemSku => $deletedItemSources) {
foreach ($fetchedSourceItems[$deletedItemSku] as $fetchedItemStockId => $fetchedItemSources) {
if ($this->getContainsAllKeys($fetchedItemSources, $deletedItemSources)) {
- $stocksToDelete[] = StockStatusIdBuilder::build(
+ $stockStatusId = StockStatusIdBuilder::build(
['stockId' => (string)$fetchedItemStockId, 'sku' => $deletedItemSku]
);
+ $stocksToDelete[$stockStatusId] = [
+ 'stock_id' => (string)$fetchedItemStockId,
+ 'sku' => $deletedItemSku
+ ];
}
}
}
diff --git a/InventoryDataExporter/Plugin/SourceItem/SourceItemUpdate.php b/InventoryDataExporter/Plugin/SourceItem/SourceItemUpdate.php
index 2dd1247a..c48b2d65 100644
--- a/InventoryDataExporter/Plugin/SourceItem/SourceItemUpdate.php
+++ b/InventoryDataExporter/Plugin/SourceItem/SourceItemUpdate.php
@@ -43,8 +43,7 @@ public function afterExecute(
SourceItemsSaveInterface $subject,
$result,
array $sourceItems
- ): void
- {
+ ): void {
$stockStatusIndexer = $this->indexer->load(self::STOCK_STATUS_FEED_INDEXER);
if (!$stockStatusIndexer->isScheduled()) {
$skus = \array_map(
diff --git a/InventoryDataExporter/README.md b/InventoryDataExporter/README.md
index a4a8fb66..d5391d02 100644
--- a/InventoryDataExporter/README.md
+++ b/InventoryDataExporter/README.md
@@ -1,9 +1,10 @@
-## Release notes
+*Magento_InventoryDataExporter* module is responsible for collecting inventory data
-*Magento_InventoryDataExporter* module
+## Stock Status
-https://docs.magento.com/user-guide/catalog/inventory-backorders.html?itm_source=devdocs&itm_medium=quick_search&itm_campaign=federated_search&itm_term=backorer
-
-
-Zero
-With Backorders enabled, entering 0 allows for infinite backorders.
\ No newline at end of file
+- Collects aggregated value of Stock Status described in [et_schema.xml](etc/et_schema.xml)
+- Depends on Inventory indexer which is used to get `isSalable` status and `qty` in stock.
+- `qtyForSale` calculated based on Reservations API
+- Stock is considered as infinite in the following cases:
+ - Manage Stock disabled
+ - [Backorders](https://docs.magento.com/user-guide/catalog/inventory-backorders.html?itm_source=devdocs&itm_medium=quick_search&itm_campaign=federated_search&itm_term=backorer) enabled and Out-of-Stock threshold is set to 0.
diff --git a/InventoryDataExporter/Test/Integration/ExportStockStatusTest.php b/InventoryDataExporter/Test/Integration/ExportStockStatusTest.php
index 8d41eba1..daff1c1c 100644
--- a/InventoryDataExporter/Test/Integration/ExportStockStatusTest.php
+++ b/InventoryDataExporter/Test/Integration/ExportStockStatusTest.php
@@ -41,6 +41,7 @@ public function testExportStockStatuses()
['sku' => 'product_in_EU_stock_with_2_sources'],
['sku' => 'product_in_Global_stock_with_3_sources'],
['sku' => 'product_with_default_stock_only'],
+ ['sku' => 'product_in_default_and_2_EU_sources'],
['sku' => 'product_with_disabled_manage_stock'],
['sku' => 'product_with_enabled_backorders'],
['sku' => 'product_in_US_stock_with_disabled_source'],
@@ -68,6 +69,45 @@ public function testExportStockStatuses()
}
}
+ /**
+ * @magentoDataFixture Magento_InventoryDataExporter::Test/_files/products_with_sources_and_reservations.php
+ */
+ public function testExportStockStatusesWithReservations()
+ {
+ $actualStockStatus = $this->processor->process(
+ 'stock_statuses',
+ [
+ ['sku' => 'product_in_EU_stock_with_2_sources'],
+ ['sku' => 'product_in_Global_stock_with_3_sources'],
+ ['sku' => 'product_with_default_stock_only'],
+ ['sku' => 'product_in_default_and_2_EU_sources'],
+ ['sku' => 'product_with_disabled_manage_stock'],
+ ['sku' => 'product_with_enabled_backorders'],
+ ['sku' => 'product_in_US_stock_with_disabled_source'],
+ ]
+ );
+
+ $actualStockStatusFormatted = [];
+ foreach ($actualStockStatus as $stockStatus) {
+ $actualStockStatusFormatted[$stockStatus['stockId']][$stockStatus['sku']] = $stockStatus;
+ }
+ foreach ($this->getExpectedStockStatusForReservations() as $stockId => $stockStatuses) {
+ foreach ($stockStatuses as $sku => $stockStatus) {
+ if (!isset($actualStockStatusFormatted[$stockId][$sku])) {
+ self::fail("Cannot find stock status for stock $stockId & sku $sku");
+ }
+ $actualStockStatus = $actualStockStatusFormatted[$stockId][$sku];
+ // ignore fields for now
+ unset($actualStockStatus['id'], $actualStockStatus['lowStock'], $actualStockStatus['updatedAt']);
+ self::assertEquals(
+ $stockStatus,
+ $actualStockStatus,
+ "Wrong stock status for stock $stockId & sku $sku"
+ );
+ }
+ }
+ }
+
/**
* @return \array[][]
*/
@@ -84,6 +124,14 @@ private function getExpectedStockStatus(): array
'infiniteStock' => false,
'isSalable' => true,
],
+ 'product_in_default_and_2_EU_sources' => [
+ 'stockId' => '1',
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'qty' => 2,
+ 'qtyForSale' => 2,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
'product_with_disabled_manage_stock' => [
'stockId' => '1',
'sku' => 'product_with_disabled_manage_stock',
@@ -119,6 +167,14 @@ private function getExpectedStockStatus(): array
'infiniteStock' => false,
'isSalable' => true,
],
+ 'product_in_default_and_2_EU_sources' => [
+ 'stockId' => '10',
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'qty' => 9.5,
+ 'qtyForSale' => 9.5,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
],
// US Stock
'20' => [
@@ -160,4 +216,115 @@ private function getExpectedStockStatus(): array
],
];
}
+
+ /**
+ * @return \array[][]
+ */
+ private function getExpectedStockStatusForReservations(): array
+ {
+ return [
+ // default stock
+ '1' => [
+ 'product_with_default_stock_only' => [
+ 'stockId' => '1',
+ 'sku' => 'product_with_default_stock_only',
+ 'qty' => 8.5,
+ 'qtyForSale' => 6.3,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ 'stockId' => '1',
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'qty' => 2.0,
+ 'qtyForSale' => 1,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_with_disabled_manage_stock' => [
+ 'stockId' => '1',
+ 'sku' => 'product_with_disabled_manage_stock',
+ 'qty' => 0,
+ 'qtyForSale' => 0,
+ 'infiniteStock' => true,
+ 'isSalable' => true,
+ ],
+ 'product_with_enabled_backorders' => [
+ 'stockId' => '1',
+ 'sku' => 'product_with_enabled_backorders',
+ 'qty' => 5.0,
+ // Uncomment it after qtyForSale will be fixed
+ //'qtyForSale' => 0,
+ 'qtyForSale' => -2.2, // JUST TEMPORARILY FIX. We should not allow negative values
+ 'infiniteStock' => true,
+ 'isSalable' => true,
+ ],
+ ],
+ // EU Stock
+ '10' => [
+ 'product_in_EU_stock_with_2_sources' => [
+ 'stockId' => '10',
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'qty' => 9.5, // 5.5 (eu-1) + 4 (eu-2)
+ 'qtyForSale' => 0,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_in_Global_stock_with_3_sources' => [
+ 'stockId' => '10',
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'qty' => 3, // eu1 + eu2
+ 'qtyForSale' => 0,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ 'stockId' => '10',
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'qty' => 9.5,
+ 'qtyForSale' => 5.5,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ ],
+ // US Stock
+ '20' => [
+ 'product_in_Global_stock_with_3_sources' => [
+ 'stockId' => '20',
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'qty' => 4, // us-1 source assigned to both stocks: US & Global
+ 'qtyForSale' => 2,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_in_US_stock_with_disabled_source' => [
+ 'stockId' => '20',
+ 'sku' => 'product_in_US_stock_with_disabled_source',
+ 'qty' => 0,
+ 'qtyForSale' => 0,
+ 'infiniteStock' => false,
+ 'isSalable' => false,
+ ],
+ ],
+ // Global Stock
+ '30' => [
+ 'product_in_Global_stock_with_3_sources' => [
+ 'stockId' => '30',
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'qty' => 5, // 1 (eu-1) + 4 (us-1)
+ 'qtyForSale' => 2.5,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ 'product_in_EU_stock_with_2_sources' => [
+ 'stockId' => '30',
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'qty' => 5.5, // eu-1 only
+ 'qtyForSale' => 5.5,
+ 'infiniteStock' => false,
+ 'isSalable' => true,
+ ],
+ ],
+ ];
+ }
}
diff --git a/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php b/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php
index 8512b31c..b45c2dd9 100644
--- a/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php
+++ b/InventoryDataExporter/Test/Integration/PartialReindexCheckTest.php
@@ -102,7 +102,11 @@ public function testSourceItemQtyUpdated()
*/
public function testSourceBulkUnassign()
{
- $skus = ['product_in_EU_stock_with_2_sources', 'product_in_Global_stock_with_3_sources', 'product_with_default_stock_only'];
+ $skus = [
+ 'product_in_EU_stock_with_2_sources',
+ 'product_in_Global_stock_with_3_sources',
+ 'product_with_default_stock_only'
+ ];
$this->bulkSourceUnassign->execute(
$skus,
diff --git a/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php b/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php
new file mode 100644
index 00000000..56a1efbc
--- /dev/null
+++ b/InventoryDataExporter/Test/Integration/UnassignProductFromStockTest.php
@@ -0,0 +1,580 @@
+stockStatusFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('stock_statuses');
+ $this->sourceItemProcessor = Bootstrap::getObjectManager()->get(SourceItemsProcessorInterface::class);
+ $this->bulkSourceUnassign = Bootstrap::getObjectManager()->get(BulkSourceUnassignInterface::class);
+ }
+
+ /**
+ * @dataProvider stocksUnassignDataProvider
+ * @param string $sku
+ * @param array $sourcesToLeave
+ * @param array $expectedData
+ * @throws InputException
+ * @throws \Zend_Db_Statement_Exception
+ * @magentoDataFixture Magento_InventoryDataExporter::Test/_files/products_with_sources.php
+ */
+ public function testSourceItemStockUnassigned(string $sku, array $sourcesToLeave, array $expectedData)
+ {
+ $sourceItems = $this->getSourcesData($sku, $sourcesToLeave);
+ $this->sourceItemProcessor->execute($sku, $sourceItems);
+
+ $feedData = $this->getFeedData([$sku]);
+
+ $this->verifyResults($feedData, $sku, $expectedData);
+ }
+
+ /**
+ * @dataProvider stocksBulkUnassignDataProvider
+ * @param array $skus
+ * @param array $sourcesToUnassign
+ * @param array $expectedData
+ * @throws InputException
+ * @throws \Zend_Db_Statement_Exception
+ * @throws \Magento\Framework\Validation\ValidationException
+ * @magentoDataFixture Magento_InventoryDataExporter::Test/_files/products_with_sources.php
+ */
+ public function testSourceItemsBulkUnassign(array $skus, array $sourcesToUnassign, array $expectedData)
+ {
+ $this->bulkSourceUnassign->execute(
+ $skus,
+ $sourcesToUnassign
+ );
+
+ $feedData = $this->getFeedData($skus);
+
+ foreach ($skus as $sku) {
+ $this->verifyResults($feedData, $sku, $expectedData[$sku]);
+ }
+ }
+
+ /**
+ * @param array $skus
+ * @return array[stock][sku]
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function getFeedData(array $skus): array
+ {
+ $output = [];
+ foreach ($this->stockStatusFeed->getFeedSince('1')['feed'] as $item) {
+ if (\in_array($item['sku'], $skus, true)) {
+ $output[$item['stockId']][$item['sku']] = $item;
+ }
+ }
+ return $output;
+ }
+
+ /**
+ * @return array[]
+ */
+ public function stocksUnassignDataProvider(): array
+ {
+ return [
+ 'one_stock_unassign' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'sources_to_leave' => ['eu-2'],
+ 'expected_data' => [
+ '10' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => false
+ ],
+ '30' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '30',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ]
+ ]
+ ],
+ 'unassign_sources_from_multiple_stocks' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'sources_to_leave' => ['eu-2'],
+ 'expected_data' => [
+ '10' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => false
+ ],
+ '20' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '20',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '30' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '30',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ]
+ ]
+ ],
+ 'default_stock_unassign_from_only_default_stock_product' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'sources_to_leave' => [],
+ 'expected_data' => [
+ '1' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ]
+ ]
+ ],
+ 'default_stock_unassign_from_default_and_custom_stocks_product' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'sources_to_leave' => ['eu-1', 'eu-2'],
+ 'expected_data' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => false
+ ]
+ ]
+ ],
+ 'custom_stock_unassign_from_default_and_custom_stocks_product' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'sources_to_leave' => ['default'],
+ 'expected_data' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => false
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ]
+ ]
+ ],
+ ];
+ }
+
+ /**
+ * @return array[]
+ */
+ public function stocksBulkUnassignDataProvider(): array
+ {
+ return [
+ 'one_stock_unassign' => [
+ 'skus' => [
+ 'product_in_EU_stock_with_2_sources',
+ 'product_in_Global_stock_with_3_sources',
+ 'product_with_default_stock_only',
+ 'product_in_default_and_2_EU_sources'
+ ],
+ 'sources_to_unassign' => ['eu-1', 'eu-2'],
+ 'expected_data' => [
+ 'product_with_default_stock_only' => [
+ '1' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'stock_id' => '1',
+ 'qty' => 8.5,
+ 'qty_for_sale' => 8.5,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 2,
+ 'qty_for_sale' => 2,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_EU_stock_with_2_sources' => [
+ '10' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_Global_stock_with_3_sources' => [
+ '10' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '30' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '30',
+ 'qty' => 4,
+ 'qty_for_sale' => 4,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ]
+ ]
+ ],
+ 'two_stocks_unassign' => [
+ 'skus' => [
+ 'product_in_EU_stock_with_2_sources',
+ 'product_in_Global_stock_with_3_sources',
+ 'product_with_default_stock_only',
+ 'product_in_default_and_2_EU_sources'
+ ],
+ 'sources_to_unassign' => ['eu-1', 'eu-2', 'us-1'],
+ 'expected_data' => [
+ 'product_with_default_stock_only' => [
+ '1' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'stock_id' => '1',
+ 'qty' => 8.5,
+ 'qty_for_sale' => 8.5,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 2,
+ 'qty_for_sale' => 2,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_EU_stock_with_2_sources' => [
+ '10' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_Global_stock_with_3_sources' => [
+ '10' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '30' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '30',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ]
+ ]
+ ],
+ 'two_stocks_and_default_unassign' => [
+ 'skus' => [
+ 'product_in_EU_stock_with_2_sources',
+ 'product_in_Global_stock_with_3_sources',
+ 'product_with_default_stock_only',
+ 'product_in_default_and_2_EU_sources'
+ ],
+ 'sources_to_unassign' => ['eu-1', 'eu-2', 'us-1', 'default'],
+ 'expected_data' => [
+ 'product_with_default_stock_only' => [
+ '1' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_EU_stock_with_2_sources' => [
+ '10' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_Global_stock_with_3_sources' => [
+ '10' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '10',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '30' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '30',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ]
+ ]
+ ],
+ 'only_default_stock_unassign' => [
+ 'skus' => [
+ 'product_in_EU_stock_with_2_sources',
+ 'product_in_Global_stock_with_3_sources',
+ 'product_with_default_stock_only',
+ 'product_in_default_and_2_EU_sources'
+ ],
+ 'sources_to_unassign' => ['default'],
+ 'expected_data' => [
+ 'product_with_default_stock_only' => [
+ '1' => [
+ 'sku' => 'product_with_default_stock_only',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ ],
+ 'product_in_default_and_2_EU_sources' => [
+ '1' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '1',
+ 'qty' => 0,
+ 'qty_for_sale' => 0,
+ 'infinite_stock' => false,
+ 'is_salable' => false,
+ 'deleted' => true
+ ],
+ '10' => [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'stock_id' => '10',
+ 'qty' => 9.5,
+ 'qty_for_sale' => 9.5,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ],
+ 'product_in_EU_stock_with_2_sources' => [
+ '10' => [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'stock_id' => '10',
+ 'qty' => 9.5,
+ 'qty_for_sale' => 9.5,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ],
+ 'product_in_Global_stock_with_3_sources' => [
+ '10' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '10',
+ 'qty' => 3,
+ 'qty_for_sale' => 3,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ '30' => [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'stock_id' => '30',
+ 'qty' => 5,
+ 'qty_for_sale' => 5,
+ 'infinite_stock' => false,
+ 'is_salable' => true,
+ 'deleted' => false
+ ],
+ ]
+ ]
+ ],
+ ];
+ }
+
+ /**
+ * @param string $sku
+ * @param array $sourcesToUnassign
+ * @return array
+ */
+ private function getSourcesData(string $sku, array $sourcesToUnassign): array
+ {
+ $sourcesData = [];
+ foreach ($sourcesToUnassign as $sourceCode) {
+ $sourcesData[] = [
+ SourceItemInterface::SOURCE_CODE => $sourceCode,
+ SourceItemInterface::SKU => $sku
+ ];
+ }
+
+ return $sourcesData;
+ }
+
+ /**
+ * @param array $feedData
+ * @param string $sku
+ * @param array $expectedData
+ */
+ private function verifyResults(array $feedData, string $sku, array $expectedData): void
+ {
+ foreach ($expectedData as $expectedStockId => $expectedStockData) {
+ self::assertEquals(
+ $expectedStockData,
+ [
+ 'sku' => $feedData[$expectedStockId][$sku]['sku'],
+ 'stock_id' => $feedData[$expectedStockId][$sku]['stockId'],
+ 'qty' => $feedData[$expectedStockId][$sku]['qty'],
+ 'qty_for_sale' => $feedData[$expectedStockId][$sku]['qtyForSale'],
+ 'infinite_stock' => $feedData[$expectedStockId][$sku]['infiniteStock'],
+ 'is_salable' => $feedData[$expectedStockId][$sku]['isSalable'],
+ 'deleted' => $feedData[$expectedStockId][$sku]['deleted'],
+ ]
+ );
+ }
+ }
+}
diff --git a/InventoryDataExporter/Test/_files/products_with_sources.php b/InventoryDataExporter/Test/_files/products_with_sources.php
index 4adb7817..99633636 100644
--- a/InventoryDataExporter/Test/_files/products_with_sources.php
+++ b/InventoryDataExporter/Test/_files/products_with_sources.php
@@ -230,6 +230,12 @@
'manage_stock' => true,
'is_qty_decimal' => true
],
+ 'product_in_default_and_2_EU_sources' => [
+ 'qty' => 11.5,
+ 'is_in_stock' => true,
+ 'manage_stock' => true,
+ 'is_qty_decimal' => true
+ ],
'product_with_disabled_manage_stock' => [
'use_config_manage_stock' => false,
'manage_stock' => false,
@@ -282,6 +288,24 @@
$sourceItemsSave = Bootstrap::getObjectManager()->get(SourceItemsSaveInterface::class);
$sourcesItemsData = [
+ [
+ SourceItemInterface::SOURCE_CODE => 'default',
+ SourceItemInterface::SKU => 'product_in_default_and_2_EU_sources',
+ SourceItemInterface::QUANTITY => 2,
+ SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
+ ],
+ [
+ SourceItemInterface::SOURCE_CODE => 'eu-1',
+ SourceItemInterface::SKU => 'product_in_default_and_2_EU_sources',
+ SourceItemInterface::QUANTITY => 5.5,
+ SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
+ ],
+ [
+ SourceItemInterface::SOURCE_CODE => 'eu-2',
+ SourceItemInterface::SKU => 'product_in_default_and_2_EU_sources',
+ SourceItemInterface::QUANTITY => 4,
+ SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
+ ],
[
SourceItemInterface::SOURCE_CODE => 'eu-1',
SourceItemInterface::SKU => 'product_in_EU_stock_with_2_sources',
diff --git a/InventoryDataExporter/Test/_files/products_with_sources_and_reservations.php b/InventoryDataExporter/Test/_files/products_with_sources_and_reservations.php
new file mode 100644
index 00000000..0b30d917
--- /dev/null
+++ b/InventoryDataExporter/Test/_files/products_with_sources_and_reservations.php
@@ -0,0 +1,102 @@
+requireDataFixture(
+ 'Magento_InventoryDataExporter::Test/_files/products_with_sources.php'
+);
+
+/**
+ * Create reservations
+ */
+$createReservations = static function (): void
+{
+ $productsList = [
+ [
+ 'sku' => 'product_with_default_stock_only',
+ 'qty_by_stocks' => [
+ ['stock_id' => 1, 'qty' => -2.2] //default stock 5.5 left
+ ],
+ ],
+ [
+ 'sku' => 'product_with_disabled_manage_stock',
+ 'qty_by_stocks' => [
+ ['stock_id' => 1, 'qty' => -2.2] //unlimited
+ ]
+ ],
+ [
+ 'sku' => 'product_with_enabled_backorders',
+ 'qty_by_stocks' => [
+ ['stock_id' => 1, 'qty' => -7.2] //unlimited
+ ]
+ ],
+ [
+ 'sku' => 'product_in_EU_stock_with_2_sources',
+ 'qty_by_stocks' => [
+ ['stock_id' => 10, 'qty' => -9.5] //eu-1, eu-2 - 4.5 left
+ ]
+ ],
+ [
+ 'sku' => 'product_in_default_and_2_EU_sources',
+ 'qty_by_stocks' => [
+ ['stock_id' => 10, 'qty' => -4], //eu-1, eu-2 - 5.5 left
+ ['stock_id' => 1, 'qty' => -1], //default - 1 left
+ ]
+ ],
+ [
+ 'sku' => 'product_in_Global_stock_with_3_sources',
+ 'qty_by_stocks' => [
+ ['stock_id' => 10, 'qty' => -3], //eu-1, eu-2 - 4.5 left
+ ['stock_id' => 20, 'qty' => -2], //us-1 - 2 left
+ ['stock_id' => 30, 'qty' => -2.5],
+ ]
+ ]
+ ];
+
+ $objectManager = Bootstrap::getObjectManager();
+ /** @var ProductRepositoryInterface $productRepository */
+ $productRepository = $objectManager->create(ProductRepositoryInterface::class);
+ /** @var ReservationBuilderInterface $reservationBuilder */
+ $reservationBuilder = $objectManager->get(ReservationBuilderInterface::class);
+ $isSourceItemManagementAllowedForProductType = $objectManager->get(
+ IsSourceItemManagementAllowedForProductTypeInterface::class
+ );
+ /** @var AppendReservationsInterface $appendReservations */
+ $appendReservations = $objectManager->get(AppendReservationsInterface::class);
+ $reservations = [];
+ foreach ($productsList as $productData) {
+ $product = $productRepository->get($productData['sku']);
+ $skusToReindex[] = $productData['sku'];
+ if ($isSourceItemManagementAllowedForProductType->execute($product->getTypeId())) {
+ foreach ($productData['qty_by_stocks'] as $stockData) {
+ $reservations[] = $reservationBuilder
+ ->setSku($productData['sku'])
+ ->setQuantity((float)$stockData['qty'])
+ ->setStockId($stockData['stock_id'])
+ ->setMetadata()
+ ->build();
+ }
+ }
+ }
+
+ $appendReservations->execute($reservations);
+};
+
+$createReservations();
diff --git a/InventoryDataExporter/Test/_files/products_with_sources_and_reservations_rollback.php b/InventoryDataExporter/Test/_files/products_with_sources_and_reservations_rollback.php
new file mode 100644
index 00000000..b1fe3f1f
--- /dev/null
+++ b/InventoryDataExporter/Test/_files/products_with_sources_and_reservations_rollback.php
@@ -0,0 +1,36 @@
+get(CleanupReservationsInterface::class);
+$cleanupReservations->execute();
+
+/** @var \Magento\Framework\App\ResourceConnection $resourceConnection */
+$resourceConnection = Bootstrap::getObjectManager()->create(\Magento\Framework\App\ResourceConnection::class);
+$connection = $resourceConnection->getConnection();
+$reservationTable = $connection->getTableName('inventory_reservation');
+
+$select = $connection->select()
+ ->from(
+ $reservationTable,
+ ['GROUP_CONCAT(' . ReservationInterface::RESERVATION_ID . ')']
+ );
+$reservationIds = implode(',', $connection->fetchCol($select));
+
+$condition = [ReservationInterface::RESERVATION_ID . ' IN (?)' => explode(',', $reservationIds)];
+$connection->delete($reservationTable, $condition);
+
+Resolver::getInstance()->requireDataFixture(
+ 'Magento_InventoryDataExporter::Test/_files/products_with_sources_rollback.php'
+);
diff --git a/InventoryDataExporter/Test/_files/products_with_sources_rollback.php b/InventoryDataExporter/Test/_files/products_with_sources_rollback.php
index b2f90af5..5ff3032d 100644
--- a/InventoryDataExporter/Test/_files/products_with_sources_rollback.php
+++ b/InventoryDataExporter/Test/_files/products_with_sources_rollback.php
@@ -7,11 +7,7 @@
*/
declare(strict_types=1);
-use Magento\Catalog\Api\Data\ProductInterfaceFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\InventoryApi\Api\Data\SourceInterfaceFactory;
-use Magento\InventoryApi\Api\Data\StockInterfaceFactory;
-use Magento\InventoryApi\Api\Data\StockSourceLinkInterfaceFactory;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Framework\App\ResourceConnection;
use Magento\InventoryApi\Api\Data\SourceInterface;
@@ -21,7 +17,8 @@
// Delete products
$skusToDelete = [
'product_with_default_stock_only', 'product_with_disabled_manage_stock', 'product_with_enabled_backorders',
- 'product_in_EU_stock_with_2_sources', 'product_in_Global_stock_with_3_sources'
+ 'product_in_EU_stock_with_2_sources', 'product_in_Global_stock_with_3_sources',
+ 'product_in_default_and_2_EU_sources'
];
$objectManager = Bootstrap::getObjectManager();
diff --git a/InventoryDataExporter/etc/di.xml b/InventoryDataExporter/etc/di.xml
index 6634a0a3..c5987a3e 100644
--- a/InventoryDataExporter/etc/di.xml
+++ b/InventoryDataExporter/etc/di.xml
@@ -63,9 +63,10 @@
-
+
+ type="Magento\InventoryDataExporter\Plugin\BulkSourceUnassign"
+ sortOrder="200"/>
@@ -75,11 +76,12 @@
-
-
-
+
+
+
+
-
-
+
+