diff --git a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php index 0f0a40c4577c..26727bca54f7 100755 --- a/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php +++ b/Inventory/Model/ResourceModel/SourceItem/SaveMultiple.php @@ -130,9 +130,11 @@ private function separateExistingAndNewItems(array $sourceItems): array $skus = []; $stock = []; + $indexedSourceItems = []; foreach ($sourceItems as $sourceItem) { $skus[] = $sourceItem->getSku(); $stock[] = $sourceItem->getSourceCode(); + $indexedSourceItems[$sourceItem->getSourceCode()][$sourceItem->getSku()] = $sourceItem; } $storedSourceItems = $connection->fetchAll( @@ -142,16 +144,20 @@ private function separateExistingAndNewItems(array $sourceItems): array ); $exisingSourceItems = []; - foreach ($sourceItems as $key => $sourceItem) { - foreach ($storedSourceItems as $storedSourceItem) { - if ($sourceItem->getSku() === $storedSourceItem['sku'] && - $sourceItem->getSourceCode() === $storedSourceItem['source_code']) { - unset($sourceItems[$key]); - $exisingSourceItems[$storedSourceItem['source_item_id']] = $sourceItem; - } + foreach ($storedSourceItems as $storedSourceItem) { + $sku = $storedSourceItem['sku']; + $sourceCode = $storedSourceItem['source_code']; + + if (isset($indexedSourceItems[$sourceCode][$sku])) { + $sourceItemId = $storedSourceItem['source_item_id']; + $exisingSourceItems[$sourceItemId] = $indexedSourceItems[$sourceCode][$sku]; + unset($indexedSourceItems[$sourceCode][$sku]); } } - return [$sourceItems, $exisingSourceItems]; + + $newSourceItems = array_merge(...array_values($indexedSourceItems)); + + return [$newSourceItems, $exisingSourceItems]; } /** diff --git a/Inventory/Test/Unit/Model/ResourceModel/SourceItem/SaveMultipleTest.php b/Inventory/Test/Unit/Model/ResourceModel/SourceItem/SaveMultipleTest.php new file mode 100644 index 000000000000..ba58014190a3 --- /dev/null +++ b/Inventory/Test/Unit/Model/ResourceModel/SourceItem/SaveMultipleTest.php @@ -0,0 +1,127 @@ +createMock(Select::class); + $connection = $this->createMock(AdapterInterface::class); + $resourceConnection = $this->createMock(ResourceConnection::class); + + $resourceConnection + ->method('getConnection') + ->willReturn($connection); + + $resourceConnection + ->method('getTableName') + ->with(SourceItemResourceModel::TABLE_NAME_SOURCE_ITEM) + ->willReturn('inventory_source_item'); + + // The code does: $connection->select()->from(...)->where(...)->where(...) + $connection + ->method('select') + ->willReturn($select); + + $select + ->method('from') + ->willReturnSelf(); + + $select + ->method('where') + ->willReturnSelf(); + + // Two items already exist in the DB + $storedItems = [ + ['source_item_id' => 1111, 'source_code' => 'warehouse', 'sku' => 'EXISTING-1'], + ['source_item_id' => 2222, 'source_code' => 'warehouse', 'sku' => 'EXISTING-2'], + ]; + + $connection + ->method('fetchAll') + ->willReturn($storedItems); + + // Capture what gets INSERTed (new items) + $insertedBind = null; + $connection + ->expects($this->once()) + ->method('query') + ->willReturnCallback(function (string $sql, array $bind) use (&$insertedBind): void { + $insertedBind = $bind; + }); + + // Capture what gets UPDATED (existing items) + $updatedItems = []; + $connection + ->expects($this->exactly(2)) + ->method('update') + ->willReturnCallback(function ( + string $tableName, + array $bind, + array $condition, + ) use (&$updatedItems): void { + $updatedItems[] = ['bind' => $bind, 'condition' => $condition]; + }); + + // Build 4 source items: 2 existing + 2 new + $sourceItems = [ + $this->createSourceItem('warehouse', 'EXISTING-1', 10, 1), + $this->createSourceItem('warehouse', 'EXISTING-2', 20, 1), + $this->createSourceItem('warehouse', 'NEW-1', 30, 1), + $this->createSourceItem('warehouse', 'NEW-2', 40, 1), + ]; + + $saveMultiple = new SaveMultiple($resourceConnection); + $saveMultiple->execute($sourceItems); + + // ASSERT — 2 new items should have been INSERTed + $this->assertIsArray($insertedBind); + $this->assertContains('NEW-1', $insertedBind); + $this->assertContains('NEW-2', $insertedBind); + $this->assertNotContains('EXISTING-1', $insertedBind); + $this->assertNotContains('EXISTING-2', $insertedBind); + + // ASSERT — 2 existing items should have been UPDATED + $this->assertContains('EXISTING-1', $updatedItems[0]['bind']); + $this->assertContains(1111, $updatedItems[0]['condition']); + $this->assertContains('EXISTING-2', $updatedItems[1]['bind']); + $this->assertContains(2222, $updatedItems[1]['condition']); + } + + /** + * Helper to create a mock SourceItemInterface. + * + * @return SourceItemInterface&MockObject + */ + private function createSourceItem( + string $sourceCode, + string $sku, + int|float $quantity, + int $status, + ): SourceItemInterface { + $item = $this->createMock(SourceItemInterface::class); + $item->method('getSourceCode')->willReturn($sourceCode); + $item->method('getSku')->willReturn($sku); + $item->method('getQuantity')->willReturn((float) $quantity); + $item->method('getStatus')->willReturn($status); + return $item; + } +}