0

How can I get a list of products that have 0 saleable quantity using either the product repository or collection (if not possible in repository).

Something like

$searchCriteria = $this->searchCriteriaBuilder->addFilter('saleable_qty', 0)->create();
$products = $this->productRepository->getList($searchCriteria);

2 Answers 2

1
  1. Salable quantity not stored in any product's table. It is maintained in inventory_stock_1 under Views. Hence you cannot get from the collection or repository directly. enter image description here
  2. To get salable quantity of the product we can use Magento\InventorySalesAdminUi\Model\GetSalableQuantityDataBySku. Please check the Get Salable Quantity in Magento 2.3
  3. Salable quantity can be checked for those product types which can maintain qty such as simple and virtual. The configurable, grouped and bundle depends upon simple products. Downloadable does not maintain qty and it will be No manage stock. enter image description hereenter image description hereenter image description hereenter image description hereenter image description hereenter image description here
  4. Please do not get confused with is_saleable value and salable quantity.
  5. Please check the attached custom script to get the list of products of salable quantity 0. If you want to do this in the module, please understand and take the logic to the module.
0

For anyone who is looking for an approach to filter products by salable status via repository.

It could be achieved by creating a custom filter and add it to the product filter processor via di (link to the devdoc)

First things first we need to create a custom filter:

<?php

declare(strict_types=1);

namespace Vendor\Module\Model\SearchCriteria\CollectionProcessor\FilterProcessor;

use Magento\Framework\Api\Filter;
use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface;
use Magento\Framework\Data\Collection\AbstractDb;

class IsSalableFilter implements CustomFilterInterface
{
    /**
     * {@inheritDoc}
     */
    public function apply(Filter $filter, AbstractDb $collection): bool
    {
        // code goes here

        return true;
    }
}

You can check Magento custom filters, they are located in the vendor/magento/module-catalog/etc/di.xml:

<virtualType name="Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\ProductFilterProcessor" type="Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor">
    <arguments>
        <argument name="customFilters" xsi:type="array">
            <item name="category_id" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductCategoryFilter</item>
            <item name="store" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductStoreFilter</item>
            <item name="store_id" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductStoreFilter</item>
            <item name="website_id" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductWebsiteFilter</item>
        </argument>
    </arguments>
</virtualType>

Now we knows which class argument we need to extend (customerFilters), so we need to add our custom filter to the custom filters array via di in our module (put this snippet into your module's etc directory to the di.xml file):

<virtualType name="Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\ProductFilterProcessor">
    <arguments>
        <argument name="customFilters" xsi:type="array">
            <item name="is_salable" xsi:type="object">Vendor\Module\Model\SearchCriteria\CollectionProcessor\FilterProcessor\IsSalableFilter</item>
        </argument>
    </arguments>
</virtualType>

After that we already able to use this filter in the search criteria for example:

$this->searchCriteriaBuilder
    ->addFilter('is_salable', 0)
    ->create();

Now we will add filtering logic to our custom is salable filter:

<?php

declare(strict_types=1);

namespace Togas\MobileApp\Model\Catalog\Product\SearchCriteria;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Framework\Api\Filter;
use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\InventoryIndexer\Indexer\IndexStructure;
use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface;
use Magento\InventorySalesApi\Model\GetAssignedStockIdForWebsiteInterface;
use Magento\Store\Model\StoreManagerInterface;

class IsSalableFilter implements CustomFilterInterface
{
    private StoreManagerInterface $storeManager;
    private GetAssignedStockIdForWebsiteInterface $getAssignedStockIdForWebsite;
    private StockIndexTableNameResolverInterface $stockIndexTableNameResolver;

    public function __construct(
        StoreManagerInterface $storeManager,
        GetAssignedStockIdForWebsiteInterface $getAssignedStockIdForWebsite,
        StockIndexTableNameResolverInterface $stockIndexTableNameResolver
    ) {
        $this->storeManager = $storeManager;
        $this->getAssignedStockIdForWebsite = $getAssignedStockIdForWebsite;
        $this->stockIndexTableNameResolver = $stockIndexTableNameResolver;
    }

    /**
     * {@inheritDoc}
     *
     * @param Collection $collection
     */
    public function apply(Filter $filter, AbstractDb $collection): bool
    {
        $websiteCode = $this->storeManager->getWebsite()->getCode();
        $stockId = $this->getAssignedStockIdForWebsite->execute($websiteCode);
        $indexTableName = $this->stockIndexTableNameResolver->execute($stockId);

        $collection->joinField(
            $filter->getField(),
            $indexTableName,
            IndexStructure::IS_SALABLE,
            sprintf('%s=%s', IndexStructure::SKU, ProductInterface::SKU),
        );

        $collection->addAttributeToFilter(
            $filter->getField(),
            [$filter->getConditionType() ?? 'eq' => $filter->getValue()]
        );

        return true;
    }
}

If you want to get only salable products in listing you need to filter by is_salable = 1, if you want not salable products set is_salable = 0 (it is the same as 0 saleable quantity)

Also, you are able to use your filter in the REST API:

/rest/V1/products/?searchCriteria[filterGroups][0][filters][0][field]=is_salable&searchCriteria[filterGroups][0][filters][0][value]=1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.