Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworking stock post processor and stock_item resolver as batch contracts #30

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/Api/Inventory/AreProductsAssignedToStockInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Api\Inventory;

interface AreProductsAssignedToStockInterface
{
/**
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array;
}
27 changes: 27 additions & 0 deletions src/Api/Inventory/AreProductsSalableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Api\Inventory;

/**
* Service which detects whether products are salable for given stock (stock data + reservations).
*
* @api
*/
interface AreProductsSalableInterface
{
/**
* Get products salable status for given SKUs and given Stock.
*
* @param string[] $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array;
}
27 changes: 27 additions & 0 deletions src/Api/Inventory/GetReservationsQuantitiesInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Api\Inventory;

/**
* Responsible for retrieving Reservation Quantity (without stock data) for SKU array
*
* @api
*/
interface GetReservationsQuantitiesInterface
{
/**
* Given a product sku array and a stock id, return reservation quantity for each sku
*
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array;
}
32 changes: 32 additions & 0 deletions src/Api/Inventory/GetStockItemsConfigurationsInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Api\Inventory;

use Magento\Framework\Exception\LocalizedException;
use Magento\InventoryConfigurationApi\Api\Data\StockItemConfigurationInterface;
use Magento\InventoryConfigurationApi\Exception\SkuIsNotAssignedToStockException;

/**
* Returns stock item configuration data by sku and stock id.
*
* @api
*/
interface GetStockItemsConfigurationsInterface
{
/**
* @param array $skuArray
* @param int $stockId
* @return StockItemConfigurationInterface[]
*/
public function execute(
array $skuArray,
int $stockId
): array;
}
36 changes: 36 additions & 0 deletions src/Api/Inventory/GetStockItemsDataInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Api\Inventory;

/**
* Responsible for retrieving StockItem Data for SKU array
*
* @api
*/
interface GetStockItemsDataInterface
{
/**
* Constants for represent fields in result array
*/
const SKU = 'sku';
const QUANTITY = 'quantity';
const IS_SALABLE = 'is_salable';

/**#@-*/

/**
* Given a product sku array and a stock id, return stock item data for each sku
*
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array;
}
113 changes: 113 additions & 0 deletions src/Model/Inventory/AreProductsAssignedToStock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Model\Inventory;

use Magento\Framework\App\ResourceConnection;
use Magento\Inventory\Model\ResourceModel\SourceItem;
use Magento\Inventory\Model\ResourceModel\StockSourceLink;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\Data\StockSourceLinkInterface;
use ScandiPWA\Performance\Api\Inventory\AreProductsAssignedToStockInterface;

class AreProductsAssignedToStock implements AreProductsAssignedToStockInterface
{
/**
* @var ResourceConnection
*/
protected ResourceConnection $resource;

/**
* @var array
*/
protected $resultsByStockAndSku = [];

/**
* @param ResourceConnection $resource
*/
public function __construct(
ResourceConnection $resource
) {
$this->resource = $resource;
}

/**
* Cache wrapper for actual loading
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array
{
$resultsBySku = [];
$loadSkus = [];

foreach ($skuArray as $sku) {
if (isset($this->resultsByStockAndSku[$stockId][$sku])) {
$resultsBySku[$sku] = $this->resultsByStockAndSku[$stockId][$sku];
} else {
$loadSkus[] = $sku;
$resultsBySku[$sku] = null;
}
}

if (count($loadSkus)) {
$results = $this->getAreProductsAssignedToStock($loadSkus, $stockId);

foreach ($results as $sku => $result) {
$this->resultsByStockAndSku[$stockId][$sku] = $result;
$resultsBySku[$sku] = $result;
}
}

return $resultsBySku;
}

/**
* Checks if products are assigned to stock, by sku list and stock id
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function getAreProductsAssignedToStock(array $skuArray, int $stockId): array
{
$finalResults = [];

foreach ($skuArray as $sku) {
$finalResults[$sku] = false;
}

$connection = $this->resource->getConnection();
$select = $connection->select()
->from(
['stock_source_link' => $this->resource->getTableName(StockSourceLink::TABLE_NAME_STOCK_SOURCE_LINK)]
)->join(
['inventory_source_item' => $this->resource->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM)],
'inventory_source_item.' . SourceItemInterface::SOURCE_CODE . '
= stock_source_link.' . SourceItemInterface::SOURCE_CODE,
['inventory_source_item.sku']
)->where(
'stock_source_link.' . StockSourceLinkInterface::STOCK_ID . ' = ?',
$stockId
)->where(
'inventory_source_item.' . SourceItemInterface::SKU . ' IN (?)',
$skuArray
)->group('inventory_source_item.' . SourceItemInterface::SKU);

$results = $connection->fetchAll($select);

foreach ($results as $result) {
if (isset($result['sku'])) {
$finalResults[$result['sku']] = true;
}
}

return $finalResults;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/**
* @category ScandiPWA_Performance
* @author Aleksandrs Mokans <[email protected]>
* @copyright Copyright (c) 2022 Scandiweb, Inc (https://scandiweb.com)
* @license http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
*/
declare(strict_types=1);

namespace ScandiPWA\Performance\Model\Inventory\AreProductsSalableCondition;

use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface;
use Magento\InventoryConfigurationApi\Api\GetStockItemConfigurationInterface;
use Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface;
use ScandiPWA\Performance\Api\Inventory\GetReservationsQuantitiesInterface;
use ScandiPWA\Performance\Api\Inventory\GetStockItemsDataInterface;
use ScandiPWA\Performance\Api\Inventory\AreProductsSalableInterface;
use ScandiPWA\Performance\Api\Inventory\GetStockItemsConfigurationsInterface;

class AreSalableWithReservationsCondition implements AreProductsSalableInterface
{
/**
* @var GetStockItemsConfigurationsInterface
*/
protected GetStockItemsConfigurationsInterface $getStockItemsConfigurations;

/**
* @var GetProductTypesBySkusInterface
*/
protected GetProductTypesBySkusInterface $getProductTypesBySkus;

/**
* @var IsSourceItemManagementAllowedForProductTypeInterface
*/
protected IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType;

/**
* @var GetStockItemsDataInterface
*/
protected GetStockItemsDataInterface $getStockItemsData;

/**
* @var GetReservationsQuantitiesInterface
*/
protected GetReservationsQuantitiesInterface $getReservationsQuantities;

/**
* @var GetStockItemConfigurationInterface
*/
protected GetStockItemConfigurationInterface $getStockItemConfiguration;

/**
* @param GetStockItemsConfigurationsInterface $getStockItemsConfigurations
* @param GetStockItemsDataInterface $getStockItemsData
* @param GetReservationsQuantitiesInterface $getReservationsQuantities
* @param IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType
* @param GetProductTypesBySkusInterface $getProductTypesBySkus
*/
public function __construct(
GetStockItemsConfigurationsInterface $getStockItemsConfigurations,
GetStockItemsDataInterface $getStockItemsData,
GetReservationsQuantitiesInterface $getReservationsQuantities,
IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType,
GetProductTypesBySkusInterface $getProductTypesBySkus
) {
$this->getStockItemsConfigurations = $getStockItemsConfigurations;
$this->getStockItemsData = $getStockItemsData;
$this->getReservationsQuantities = $getReservationsQuantities;
$this->isSourceItemManagementAllowedForProductType = $isSourceItemManagementAllowedForProductType;
$this->getProductTypesBySkus = $getProductTypesBySkus;
}

/**
* @param array $skuArray
* @param int $stockId
* @return array
*/
public function execute(array $skuArray, int $stockId): array
{
$result = [];
$skusToCheck = array_flip($skuArray);

$stockItemDataArray = $this->getStockItemsData->execute($skuArray, $stockId);

foreach ($stockItemDataArray as $sku => $stockItemData) {
if (null === $stockItemData) {
// Sku is not assigned to Stock
$result[$sku] = false;
unset($skusToCheck[$sku]);
continue;
}

// these values will be taken from cache, so can be executed individually
$productType = $this->getProductTypesBySkus->execute([$sku])[$sku];

// source item management not active for product type, do not check reservations
if (false === $this->isSourceItemManagementAllowedForProductType->execute($productType)) {
$result[$sku] = (bool)$stockItemData[GetStockItemsDataInterface::IS_SALABLE];
unset($skusToCheck[$sku]);
}
}

if (count($skusToCheck)) {
// need to check reservations for the remaining skus
$stockItemConfigurations = $this->getStockItemsConfigurations->execute(array_keys($skusToCheck), $stockId);
$reservationQtys = $this->getReservationsQuantities->execute(array_keys($skusToCheck), $stockId);

foreach ($stockItemConfigurations as $sku => $stockItemConfiguration) {
$qtyWithReservation = $stockItemDataArray[$sku][GetStockItemsDataInterface::QUANTITY] + $reservationQtys[$sku];
$result[$sku] = $qtyWithReservation > $stockItemConfiguration->getMinQty();
}
}

return $result;
}
}
Loading