From b2056b27fe9a1b5677fb377f9e96dd5eed887a77 Mon Sep 17 00:00:00 2001 From: Roman Bondarenko Date: Thu, 18 Jul 2024 11:04:02 +0300 Subject: [PATCH 1/2] feat(#38488): check source_code integrity during source_item mass save --- .../ResourceModel/SourceItem/SaveMultiple.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php index 75f17730a1d..2847987387a 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,39 @@ public function execute(array $sourceItems) } } + /** + * @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 * From 44556b1fe10ba2e223e458c73505316d8eb33b43 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Jul 2024 10:56:46 +0300 Subject: [PATCH 2/2] feat(#38488): test check source_code integrity during source_item mass save --- .../ResourceModel/SourceItem/SaveMultiple.php | 2 + .../Test/Api/SourceItemsSave/SaveTest.php | 68 ++++++++++++------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php index 2847987387a..1fafda16350 100755 --- a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php +++ b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php @@ -61,6 +61,8 @@ public function execute(array $sourceItems) } /** + * Ensure stocks for incoming items are exists + * * @param SourceItemInterface[] $sourceItems * @return void * 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]);