diff --git a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php index 75f17730a1d..1fafda16350 100755 --- a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php +++ b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php @@ -9,6 +9,8 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Inventory\Model\ResourceModel\Source as SourceResourceModel; use Magento\Inventory\Model\ResourceModel\SourceItem as SourceItemResourceModel; use Magento\InventoryApi\Api\Data\SourceItemInterface; @@ -47,6 +49,8 @@ public function execute(array $sourceItems) $connection = $this->resourceConnection->getConnection(); $tableName = $this->resourceConnection->getTableName(SourceItemResourceModel::TABLE_NAME_SOURCE_ITEM); + $this->assertSourceCodesIntegrity($sourceItems); + [$newItems, $existingItems] = $this->separateExistingAndNewItems($sourceItems); if (count($newItems)) { $this->insertNewItems($newItems, $connection, $tableName); @@ -56,6 +60,41 @@ public function execute(array $sourceItems) } } + /** + * Ensure stocks for incoming items are exists + * + * @param SourceItemInterface[] $sourceItems + * @return void + * + * @throws LocalizedException + */ + private function assertSourceCodesIntegrity($sourceItems) + { + $sourceCodes = array_unique(array_map(function($sourceItem) { + return $sourceItem->getSourceCode(); + }, $sourceItems)); + + $sourcesCheckSql = sprintf( + 'SELECT `%s` FROM `%s` WHERE `%s` in (%s)', + SourceItemInterface::SOURCE_CODE, + $this->resourceConnection->getTableName(SourceResourceModel::TABLE_NAME_SOURCE), + SourceItemInterface::SOURCE_CODE, + implode(', ', array_map(function ($code) { + return '"' . $code . '"'; + }, $sourceCodes)), + ); + + $existingSources = $this->resourceConnection + ->getConnection() + ->query($sourcesCheckSql) + ->fetchAll(\Zend_Db::FETCH_COLUMN); + + $missing = array_diff($sourceCodes, $existingSources); + if (count($missing) > 0) { + throw new LocalizedException(__("Requested source(s) was not found by the source_code: %1", implode(', ', $missing))); + } + } + /** * Build column sql part * diff --git a/InventoryApi/Test/Api/SourceItemsSave/SaveTest.php b/InventoryApi/Test/Api/SourceItemsSave/SaveTest.php index 9712ba8d7fd..b81a24c4bb4 100644 --- a/InventoryApi/Test/Api/SourceItemsSave/SaveTest.php +++ b/InventoryApi/Test/Api/SourceItemsSave/SaveTest.php @@ -23,16 +23,9 @@ class SaveTest extends WebapiAbstract const SERVICE_NAME_DELETE = 'inventoryApiSourceItemsDeleteV1'; /**#@-*/ - /** - * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php - * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php - * - * @see https://app.hiptest.com/projects/69435/test-plan/folders/529092/scenarios/1824126 - * @see https://app.hiptest.com/projects/69435/test-plan/folders/530616/scenarios/1824143 - */ - public function testExecute() + private function createSourceItemsStub() { - $sourceItems = [ + return [ [ SourceItemInterface::SOURCE_CODE => 'eu-1', SourceItemInterface::SKU => 'SKU-1', @@ -46,6 +39,18 @@ public function testExecute() SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK, ], ]; + } + + /** + * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php + * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php + */ + public function testSourceIntegrityFails() + { + $sourceItems = $this->createSourceItemsStub(); + foreach ($sourceItems as $item) { + $item[SourceItemInterface::SOURCE_CODE] = strtoupper($item[SourceItemInterface::SOURCE_CODE]); + } $serviceInfo = [ 'rest' => [ @@ -61,26 +66,42 @@ public function testExecute() $actualData = $this->getSourceItems(); - self::assertEquals(2, $actualData['total_count']); - AssertArrayContains::assert($sourceItems, $actualData['items']); + self::assertEquals(0, $actualData['total_count']); } - protected function tearDown(): void + /** + * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php + * @magentoApiDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php + * + * @see https://app.hiptest.com/projects/69435/test-plan/folders/529092/scenarios/1824126 + * @see https://app.hiptest.com/projects/69435/test-plan/folders/530616/scenarios/1824143 + */ + public function testExecute() { - $sourceItems = [ - [ - SourceItemInterface::SOURCE_CODE => 'eu-1', - SourceItemInterface::SKU => 'SKU-1', - SourceItemInterface::QUANTITY => 5.5, - SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK, + $sourceItems = $this->createSourceItemsStub(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, ], - [ - SourceItemInterface::SOURCE_CODE => 'eu-2', - SourceItemInterface::SKU => 'SKU-1', - SourceItemInterface::QUANTITY => 3, - SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK, + 'soap' => [ + 'service' => self::SERVICE_NAME_SAVE, + 'operation' => self::SERVICE_NAME_SAVE . 'Execute', ], ]; + $this->_webApiCall($serviceInfo, ['sourceItems' => $sourceItems]); + + $actualData = $this->getSourceItems(); + + self::assertEquals(2, $actualData['total_count']); + AssertArrayContains::assert($sourceItems, $actualData['items']); + } + + protected function tearDown(): void + { + $sourceItems = $sourceItems = $this->createSourceItemsStub(); + $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . '?' @@ -92,6 +113,7 @@ protected function tearDown(): void 'operation' => self::SERVICE_NAME_DELETE . 'Execute', ], ]; + (TESTS_WEB_API_ADAPTER === self::ADAPTER_REST) ? $this->_webApiCall($serviceInfo) : $this->_webApiCall($serviceInfo, ['sourceItems' => $sourceItems]);