Skip to content

Commit

Permalink
Merge pull request #13 from magento-troll/MDEE-40-tests
Browse files Browse the repository at this point in the history
Cover stock status data export with tests
  • Loading branch information
mslabko committed Nov 2, 2021
2 parents bfff3a4 + 1cbfd19 commit 3f7a728
Show file tree
Hide file tree
Showing 15 changed files with 1,088 additions and 50 deletions.
4 changes: 1 addition & 3 deletions InventoryDataExporter/Model/Provider/StockStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions InventoryDataExporter/Model/Query/InventoryStockQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
106 changes: 98 additions & 8 deletions InventoryDataExporter/Model/Query/StockStatusDeleteQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

/**
Expand All @@ -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 = [];
Expand All @@ -63,21 +82,92 @@ 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
*
* @param array $idsToDelete
*/
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())
];
}
}
53 changes: 37 additions & 16 deletions InventoryDataExporter/Plugin/BulkSourceUnassign.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

/**
Expand Down
6 changes: 5 additions & 1 deletion InventoryDataExporter/Plugin/MarkItemsAsDeleted.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
];
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions InventoryDataExporter/Plugin/SourceItem/SourceItemUpdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
15 changes: 8 additions & 7 deletions InventoryDataExporter/README.md
Original file line number Diff line number Diff line change
@@ -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.
- 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.

0 comments on commit 3f7a728

Please sign in to comment.