Skip to content

Commit

Permalink
Merge pull request #217 from magento-l3/L3-PR-2022-04-19
Browse files Browse the repository at this point in the history
L3-PR-2022-04-19
  • Loading branch information
viktym authored May 4, 2022
2 parents 17a6164 + 93973ce commit e08a47c
Show file tree
Hide file tree
Showing 11 changed files with 485 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
<section name="AdminEditStockGeneralSection">
<element name="name" type="input" selector=".admin__control-text[name='general[name]']"/>
<element name="disabledAddingSourcesNotice" type="text" selector="//strong[contains(text(), 'NOTE')]/following-sibling::span"/>
</section>
</sections>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="AdminAssertNoteIsDisplayedForAssignSourcesForDefaultStockTest">
<annotations>
<stories value="Assign new sources to the default stock"/>
<title value="Assert that a note is displayed for Assign Sources for Default Stock"/>
<description value="User shouldn't be able to assign new sources to the default stock"/>
<testCaseId value="AC-2761"/>
<useCaseId value="ACP2E-545"/>
<severity value="AVERAGE"/>
<group value="msi"/>
<group value="multi_mode"/>
</annotations>

<before>
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
</before>

<after>
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/>
</after>

<amOnPage url="{{AdminManageStockPage.url}}" stepKey="amOnStockListPage"/>
<click selector="{{AdminGridRow.editByValue(_defaultStock.name)}}" stepKey="clickEditDefaultStock"/>
<waitForPageLoad time="20" stepKey="waitFroDefaultStockEditPageLoad"/>

<!-- Check that Assign Sources button is disabled -->
<assertElementContainsAttribute stepKey="seeAssignSourceButtonIsDisabled">
<expectedResult selector="{{AdminEditStockSourcesSection.assignSources}}" attribute="disabled" type="string"></expectedResult>
</assertElementContainsAttribute>

<grabTextFrom selector="{{AdminEditStockGeneralSection.disabledAddingSourcesNotice}}" stepKey="grabNote"/>
<assertEquals stepKey="assertNotification">
<actualResult type="const">$grabNote</actualResult>
<expectedResult type="const">"Assign sources is disabled for default stock"</expectedResult>
</assertEquals>
</test>
</tests>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
</div>

<div class="admin__field-note" if="$data.notice">
<span><strong translate="NOTE"></strong>: <translate args="notice"></translate></span>
<span><strong translate="'NOTE'"></strong>: <translate args="notice"></translate></span>
</div>
</div>
138 changes: 138 additions & 0 deletions InventoryIndexer/Test/Api/SourceItemsSaveInterfaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\InventoryIndexer\Test\Api;

use Magento\CatalogInventory\Model\Stock;
use Magento\Framework\Webapi\Rest\Request;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryIndexer\Model\ResourceModel\GetStockItemData;
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\WebapiAbstract;

class SourceItemsSaveInterfaceTest extends WebapiAbstract
{
private const RESOURCE_PATH = '/V1/inventory/source-items';
private const SERVICE_NAME_SAVE = 'inventoryApiSourceItemsSaveV1';

/**
* @var GetStockItemData
*/
private $getStockItemData;

/**
* @inheritdoc
*/
protected function setUp(): void
{
parent::setUp();
$this->getStockItemData = Bootstrap::getObjectManager()->get(GetStockItemData::class);
}

/**
* @magentoApiDataFixture Magento/Catalog/_files/products.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/stock_with_source_link.php
* @magentoApiDataFixture Magento/Catalog/_files/reindex_catalog_inventory_stock.php
* @magentoApiDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory_rollback.php
*/
public function testProductSalabilityShouldChangeAfterUpdatingSourceItemDefaultStock(): void
{
$sku = 'simple';
$sourceCode = 'default';
$stockId = Stock::DEFAULT_STOCK_ID;
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertTrue((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
$this->saveSourceItems(
[
[
SourceItemInterface::SOURCE_CODE => $sourceCode,
SourceItemInterface::SKU => $sku,
SourceItemInterface::QUANTITY => 0,
SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
],
]
);
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertFalse((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
$this->saveSourceItems(
[
[
SourceItemInterface::SOURCE_CODE => $sourceCode,
SourceItemInterface::SKU => $sku,
SourceItemInterface::QUANTITY => 10,
SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
],
]
);
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertTrue((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
}

/**
* @magentoApiDataFixture Magento_InventorySalesApi::Test/_files/websites_with_stores.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/sources.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/stocks.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/stock_source_links.php
* @magentoApiDataFixture Magento_InventorySalesApi::Test/_files/stock_website_sales_channels.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/products.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/source_items.php
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/assign_products_to_websites.php
* @magentoApiDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php
*/
public function testProductSalabilityShouldChangeAfterUpdatingSourceItemCustomStock(): void
{
$sku = 'SKU-2';
$sourceCode = 'us-1';
$stockId = 20;
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertTrue((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
$this->saveSourceItems(
[
[
SourceItemInterface::SOURCE_CODE => $sourceCode,
SourceItemInterface::SKU => $sku,
SourceItemInterface::QUANTITY => 0,
SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
],
]
);
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertFalse((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
$this->saveSourceItems(
[
[
SourceItemInterface::SOURCE_CODE => $sourceCode,
SourceItemInterface::SKU => $sku,
SourceItemInterface::QUANTITY => 10,
SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
],
]
);
$stockData = $this->getStockItemData->execute($sku, $stockId);
$this->assertTrue((bool)$stockData[GetStockItemDataInterface::IS_SALABLE]);
}

/**
* @param array $sourceItems
* @return void
*/
private function saveSourceItems(array $sourceItems): void
{
$serviceInfo = [
'rest' => [
'resourcePath' => self::RESOURCE_PATH,
'httpMethod' => Request::HTTP_METHOD_POST,
],
'soap' => [
'service' => self::SERVICE_NAME_SAVE,
'operation' => self::SERVICE_NAME_SAVE . 'Execute',
],
];
$this->_webApiCall($serviceInfo, ['sourceItems' => $sourceItems]);
}
}
10 changes: 10 additions & 0 deletions InventoryIndexer/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,14 @@
<argument name="productTableName" xsi:type="string">catalog_product_entity</argument>
</arguments>
</type>
<virtualType name="Magento\InventoryIndexer\Model\AreProductsSalable" type="Magento\InventorySales\Model\AreProductsSalable">
<arguments>
<argument name="isProductSalable" xsi:type="object">Magento\InventoryIndexer\Model\IsProductSalable</argument>
</arguments>
</virtualType>
<type name="Magento\InventoryIndexer\Indexer\SourceItem\GetSalableStatuses">
<arguments>
<argument name="areProductsSalable" xsi:type="object">Magento\InventoryIndexer\Model\AreProductsSalable</argument>
</arguments>
</type>
</config>
76 changes: 76 additions & 0 deletions InventorySales/Model/GetProductAvailableQty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\InventorySales\Model;

use Magento\Framework\App\ResourceConnection;
use Magento\Inventory\Model\ResourceModel\SourceItem;
use Magento\Inventory\Model\ResourceModel\StockSourceLink;
use Magento\Inventory\Model\ResourceModel\Source;
use Magento\InventoryApi\Api\Data\SourceInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\Data\StockSourceLinkInterface;
use Zend_Db_Expr;

/**
* Service which returns aggregated quantity of a product across all active sources in the provided stock
*/
class GetProductAvailableQty
{
/**
* @var ResourceConnection
*/
private $resourceConnection;

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

/**
* Get available quantity for given SKU and Stock
*
* @param string $sku
* @param int $stockId
* @return float
*/
public function execute(string $sku, int $stockId): float
{
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()->from(
['issl' => $this->resourceConnection->getTableName(StockSourceLink::TABLE_NAME_STOCK_SOURCE_LINK)],
[]
)->joinInner(
['is' => $this->resourceConnection->getTableName(Source::TABLE_NAME_SOURCE)],
sprintf('issl.%s = is.%s', StockSourceLinkInterface::SOURCE_CODE, SourceInterface::SOURCE_CODE),
[]
)->joinInner(
['isi' => $this->resourceConnection->getTableName(SourceItem::TABLE_NAME_SOURCE_ITEM)],
sprintf('issl.%s = isi.%s', StockSourceLinkInterface::SOURCE_CODE, SourceItemInterface::SOURCE_CODE),
[]
)->where(
sprintf('issl.%s = ?', StockSourceLinkInterface::STOCK_ID),
$stockId
)->where(
sprintf('is.%s = ?', SourceInterface::ENABLED),
1
)->where(
sprintf('isi.%s = ?', SourceItemInterface::SKU),
$sku
)->where(
sprintf('isi.%s = ?', SourceItemInterface::STATUS),
SourceItemInterface::STATUS_IN_STOCK
)->columns(
['quantity' => new Zend_Db_Expr(sprintf('SUM(isi.%s)', SourceItemInterface::QUANTITY))]
);

return (float) $connection->fetchOne($select);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface;
use Magento\InventoryConfigurationApi\Api\Data\StockItemConfigurationInterface;
use Magento\InventoryReservationsApi\Model\GetReservationsQuantityInterface;
use Magento\InventorySales\Model\GetProductAvailableQty;
use Magento\InventorySalesApi\Api\IsProductSalableInterface;
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
use Magento\InventoryConfigurationApi\Api\GetStockItemConfigurationInterface;
Expand Down Expand Up @@ -45,25 +46,33 @@ class IsSalableWithReservationsCondition implements IsProductSalableInterface
*/
private $getProductTypesBySkus;

/**
* @var GetProductAvailableQty
*/
private $getProductAvailableQty;

/**
* @param GetStockItemDataInterface $getStockItemData
* @param GetReservationsQuantityInterface $getReservationsQuantity
* @param GetStockItemConfigurationInterface $getStockItemConfiguration
* @param IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType
* @param GetProductTypesBySkusInterface $getProductTypesBySkus
* @param GetProductAvailableQty $getProductAvailableQty
*/
public function __construct(
GetStockItemDataInterface $getStockItemData,
GetReservationsQuantityInterface $getReservationsQuantity,
GetStockItemConfigurationInterface $getStockItemConfiguration,
IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType,
GetProductTypesBySkusInterface $getProductTypesBySkus
GetProductTypesBySkusInterface $getProductTypesBySkus,
GetProductAvailableQty $getProductAvailableQty
) {
$this->getStockItemData = $getStockItemData;
$this->getReservationsQuantity = $getReservationsQuantity;
$this->getStockItemConfiguration = $getStockItemConfiguration;
$this->isSourceItemManagementAllowedForProductType = $isSourceItemManagementAllowedForProductType;
$this->getProductTypesBySkus = $getProductTypesBySkus;
$this->getProductAvailableQty = $getProductAvailableQty;
}

/**
Expand All @@ -84,7 +93,7 @@ public function execute(string $sku, int $stockId): bool

/** @var StockItemConfigurationInterface $stockItemConfiguration */
$stockItemConfiguration = $this->getStockItemConfiguration->execute($sku, $stockId);
$qtyWithReservation = $stockItemData[GetStockItemDataInterface::QUANTITY] +
$qtyWithReservation = $this->getProductAvailableQty->execute($sku, $stockId) +
$this->getReservationsQuantity->execute($sku, $stockId);

return $qtyWithReservation > $stockItemConfiguration->getMinQty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ public function execute(Select $select): string
$itemBackordersCondition = 'legacy_stock_item.backorders <> ' . StockItemConfigurationInterface::BACKORDERS_NO;
$useDefaultBackorders = 'legacy_stock_item.use_config_backorders';
$itemMinQty = 'legacy_stock_item.min_qty';
$itemQty = 'legacy_stock_item.qty';
$globalMinQty = (float) $this->configuration->getMinQty();
$minQty = (string) $select->getConnection()->getCheckSql(
'legacy_stock_item.use_config_min_qty = 1',
$globalMinQty,
$itemMinQty
);

$isBackorderEnabled = $globalBackorders === StockItemConfigurationInterface::BACKORDERS_NO
? $useDefaultBackorders . ' = ' . StockItemConfigurationInterface::BACKORDERS_NO . ' AND ' .
Expand All @@ -55,6 +60,6 @@ public function execute(Select $select): string
1
);

return "($isBackorderEnabled) AND ($itemMinQty >= 0 OR $itemQty > $itemMinQty) AND SUM($isAnyStockItemInStock)";
return "($isBackorderEnabled) AND ($minQty >= 0) AND SUM($isAnyStockItemInStock)";
}
}
Loading

0 comments on commit e08a47c

Please sign in to comment.