Model/Write/Products/CollectionDecorator/StockData/SourceItemMapProvider.php
<?php
namespace Tweakwise\Magento2TweakwiseExport\Model\Write\Products\CollectionDecorator\StockData;
use Tweakwise\Magento2TweakwiseExport\Model\StockItem;
use Tweakwise\Magento2TweakwiseExport\Model\StockItemFactory as TweakwiseStockItemFactory;
use Tweakwise\Magento2TweakwiseExport\Model\Write\Products\Collection;
use Tweakwise\Magento2TweakwiseExport\Model\DbResourceHelper;
use Magento\Framework\Exception\LocalizedException;
use Magento\InventoryApi\Api\Data\SourceInterface;
use Tweakwise\Magento2TweakwiseExport\Model\StockSourceProviderFactory;
use Tweakwise\Magento2TweakwiseExport\Model\StockResolverFactory;
use Tweakwise\Magento2TweakwiseExport\Model\DefaultStockProviderInterfaceFactory;
use Magento\InventorySalesApi\Api\StockResolverInterface;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\InventoryApi\Api\GetSourcesAssignedToStockOrderedByPriorityInterface;
use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface;
use Zend_Db_Expr;
use Tweakwise\Magento2TweakwiseExport\Model\Write\Stock\Collection as StockCollection;
/**
* Class DefaultImplementation
*/
class SourceItemMapProvider implements StockMapProviderInterface
{
/**
* @var TweakwiseStockItemFactory
*/
protected $tweakwiseStockItemFactory;
/**
* @var StoreManagerInterface
*/
protected $storeManager;
/**
* @var StockResolverInterface
*/
protected $stockResolver;
/**
* @var GetSourcesAssignedToStockOrderedByPriorityInterface
*/
protected $stockSourceProvider;
/**
* @var StockSourceProviderFactory
*/
protected $stockSourceProviderFactory;
/**
* @var StockResolverFactory
*/
protected $stockResolverFactory;
/**
* @var DbResourceHelper
*/
protected $dbResource;
/**
* @var DefaultStockProviderInterfaceFactory
*/
protected $defaultStockProviderFactory;
/**
* @var DefaultStockProviderInterface
*/
protected $defaultStockProvider;
/**
* StockData constructor.
*
* @param DbResourceHelper $dbResource
* @param StockSourceProviderFactory $stockSourceProviderFactory
* @param TweakwiseStockItemFactory $tweakwiseStockItemFactory
* @param StoreManagerInterface $storeManager
* @param StockResolverFactory $stockResolverFactory
* @param DefaultStockProviderInterfaceFactory $defaultStockProviderFactory
* @param DbResourceHelper $resourceHelper
*/
public function __construct(
DbResourceHelper $dbResource,
StockSourceProviderFactory $stockSourceProviderFactory,
TweakwiseStockItemFactory $tweakwiseStockItemFactory,
StoreManagerInterface $storeManager,
StockResolverFactory $stockResolverFactory,
DefaultStockProviderInterfaceFactory $defaultStockProviderFactory,
DbResourceHelper $resourceHelper
) {
$this->dbResource = $dbResource;
$this->stockSourceProviderFactory = $stockSourceProviderFactory;
$this->tweakwiseStockItemFactory = $tweakwiseStockItemFactory;
$this->storeManager = $storeManager;
$this->stockResolverFactory = $stockResolverFactory;
$this->dbResource = $resourceHelper;
$this->defaultStockProviderFactory = $defaultStockProviderFactory;
}
/**
* @param Collection|StockCollection $collection
* @return StockItem[]
* @throws LocalizedException
* @throws \Zend_Db_Statement_Exception
* phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function getStockItemMap(Collection|StockCollection $collection): array
{
if ($collection->count() === 0) {
return [];
}
$entityIds = $collection->getAllIds();
$store = $collection->getStore();
$sourceCodes = $this->getSourceCodesForStore($store);
$stockId = $this->getStockIdForStoreId($store);
$dbConnection = $this->dbResource->getConnection();
$sourceItemTableName = $this->dbResource->getTableName('inventory_source_item');
$reservationTableName = $this->dbResource->getTableName('inventory_reservation');
$productTableName = $this->dbResource->getTableName('catalog_product_entity');
$stockItemTable = $this->dbResource->getTableName('cataloginventory_stock_item');
$reservationSelect = $dbConnection
->select()
->from($reservationTableName)
->where('stock_id = ?', $stockId)
->reset('columns')
->columns(
[
'sku',
'stock_id',
'r_quantity' => "SUM(`$reservationTableName`.`quantity`)"
]
)
->group("$reservationTableName.sku");
// When stock id is default apparently the standard stock items are still used.
// Todo We should check if we can use magento's api for this as this is feeling rather sensitive.
if ($this->getDefaultStockProvider()->getId() !== $stockId) {
$sourceItemSelect = $dbConnection
->select()
->from($sourceItemTableName)
->reset('columns')
->where("$sourceItemTableName.source_code IN (?)", $sourceCodes)
->columns(
[
'sku',
's_quantity' => "SUM($sourceItemTableName.quantity)",
's_status' => "MAX($sourceItemTableName.status)"
]
)
->group("$sourceItemTableName.sku");
} else {
$sourceItemSelect = $dbConnection
->select()
->from($stockItemTable)
->reset('columns')
->where("$stockItemTable.product_id IN (?)", $entityIds)
/*
$stock_id is in this case the default stock id (i.e. 1) this filter problably doesnt remove anything
but it is here just to be sure.
*/
->where("$stockItemTable.stock_id = ?", $stockId)
->columns(
[
'product_id',
's_quantity' => "$stockItemTable.qty",
's_status' => "$stockItemTable.is_in_stock"
]
);
}
$select = $dbConnection
->select()
->from($productTableName)
->reset('columns');
// When stock id is default apparently the standard stock items are still used.
// Todo We should check if we can use magento's api for this as this is feeling rather sensitive.
if ($this->getDefaultStockProvider()->getId() !== $stockId) {
$select->joinLeft(
['s' => $sourceItemSelect],
"s.sku = $productTableName.sku",
[]
);
} else {
$select->joinLeft(
['s' => $sourceItemSelect],
"s.product_id = $productTableName.entity_id",
[]
);
}
$select->joinLeft(
['r' => $reservationSelect],
"r.sku = $productTableName.sku AND r.stock_id = $stockId",
[]
);
$select->join(
$stockItemTable,
"$stockItemTable.product_id = $productTableName.entity_id",
[
'backorders',
]
)
->where("$productTableName.entity_id IN (?)", $entityIds)
->columns(
[
'product_entity_id' => "$productTableName.entity_id",
'qty' => new Zend_Db_Expr('COALESCE(s.s_quantity,0) + COALESCE(r.r_quantity,0)'),
'is_in_stock' => 'COALESCE(s.s_status,0)'
]
);
$result = $select->query();
$map = [];
while ($row = $result->fetch()) {
$map[$row['product_entity_id']] = $this->getTweakwiseStockItem($row);
}
return $map;
}
/**
* @param Store $store
* @return array|null[]|string[]
* @throws LocalizedException
*/
protected function getSourceCodesForStore(Store $store): array
{
$stockId = $this->getStockIdForStoreId($store);
$sourceModels = $this->getStockSourceProvider()->execute($stockId);
//don't get stock for disabled stock sources
foreach ($sourceModels as $key => $sourceModel) {
if (!$sourceModel->isEnabled()) {
unset($sourceModels[$key]);
}
}
$sourceCodeMapper = static function (SourceInterface $source) {
return $source->getSourceCode();
};
return array_map($sourceCodeMapper, $sourceModels);
}
/**
* This is necessary to remain compatible with Magento 2.2.X
* setup:di:compile fails when there is a reference to a non existing Interface or Class in the constructor
*
* @return GetSourcesAssignedToStockOrderedByPriorityInterface
*/
protected function getStockSourceProvider(): GetSourcesAssignedToStockOrderedByPriorityInterface
{
if (!$this->stockSourceProvider) {
$this->stockSourceProvider = $this->stockSourceProviderFactory->create();
}
return $this->stockSourceProvider;
}
/**
* @return DefaultStockProviderInterface
*/
protected function getDefaultStockProvider(): DefaultStockProviderInterface
{
if (!$this->defaultStockProvider) {
$this->defaultStockProvider = $this->defaultStockProviderFactory->create();
}
return $this->defaultStockProvider;
}
/**
* @param Store $store
* @return int|null
* @throws LocalizedException
*/
protected function getStockIdForStoreId(Store $store): ?int
{
$websiteCode = $store->getWebsite()->getCode();
return $this->getStockResolver()->execute('website', $websiteCode)->getStockId();
}
/**
* This is necessary to remain compatible with Magento 2.2.X
* setup:di:compile fails when there is a reference to a non existing Interface or Class in the constructor
*
* @return StockResolverInterface
*/
protected function getStockResolver(): StockResolverInterface
{
if (!$this->stockResolver) {
$this->stockResolver = $this->stockResolverFactory->create();
}
return $this->stockResolver;
}
/**
* @param array $item
* @return StockItem
*/
protected function getTweakwiseStockItem(array $item): StockItem
{
$tweakwiseStockItem = $this->tweakwiseStockItemFactory->create();
$qty = (int)$item['qty'];
$isInStock = max((int)$item['backorders'], (int)$item['is_in_stock']);
$tweakwiseStockItem->setQty($qty);
$tweakwiseStockItem->setIsInStock($isInStock);
return $tweakwiseStockItem;
}
}