diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f6048b7..e454bbfa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# NEXT + +- Fixed Shopware 5 customer migration with custom locales by resolving `languageId` from existing migration mappings during the same run + # 16.2.0 - #15568 - Switched to PHP Symfony service definitions diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md index ea3b5a9fd..1217c7b3e 100644 --- a/CHANGELOG_de-DE.md +++ b/CHANGELOG_de-DE.md @@ -1,3 +1,7 @@ +# NEXT + +- Fehler bei der Migration von Shopware-5-Kunden mit benutzerdefinierten Sprachen behoben, indem die `languageId` im selben Lauf aus vorhandenen Migrations-Mappings aufgelöst wird + # 16.2.0 - #15568 - Umstellung auf PHP-Symfony-Service-Definitionen diff --git a/src/Profile/Shopware/Converter/CustomerConverter.php b/src/Profile/Shopware/Converter/CustomerConverter.php index 2f4a95d20..979fbe7b7 100644 --- a/src/Profile/Shopware/Converter/CustomerConverter.php +++ b/src/Profile/Shopware/Converter/CustomerConverter.php @@ -218,7 +218,12 @@ public function convert( unset($data['attributes']); if (isset($data['customerlanguage']['locale'])) { - $languageUuid = $this->languageLookup->get($data['customerlanguage']['locale'], $context); + $languageUuid = $this->resolveLanguageId( + $data['customerlanguage']['locale'], + $this->languageLookup, + $context, + ); + if ($languageUuid !== null) { $converted['languageId'] = $languageUuid; } @@ -326,19 +331,19 @@ protected function applyAddresses(array &$originalData, array &$converted): void $newAddress = []; $salutationUuid = $this->getSalutation($address['salutation']); - if ($salutationUuid === null) { - continue; - } - $addressMapping = $this->mappingService->getOrCreateMapping( $this->connectionId, DefaultEntities::CUSTOMER_ADDRESS, $address['id'], $this->context ); + $newAddress['id'] = $addressMapping['entityId']; $this->mappingIds[] = $addressMapping['id']; - $newAddress['salutationId'] = $salutationUuid; + + if ($salutationUuid !== null) { + $newAddress['salutationId'] = $salutationUuid; + } if (isset($originalData['default_billing_address_id']) && $address['id'] === $originalData['default_billing_address_id']) { $converted['defaultBillingAddressId'] = $newAddress['id']; diff --git a/src/Profile/Shopware/Converter/ShopwareConverter.php b/src/Profile/Shopware/Converter/ShopwareConverter.php index 2c0c8961d..587c68526 100644 --- a/src/Profile/Shopware/Converter/ShopwareConverter.php +++ b/src/Profile/Shopware/Converter/ShopwareConverter.php @@ -11,6 +11,8 @@ use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\Connection\Helper\ConnectionNameSanitizer; use SwagMigrationAssistant\Migration\Converter\Converter; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; +use SwagMigrationAssistant\Migration\Mapping\Lookup\LanguageLookup; use SwagMigrationAssistant\Migration\MigrationContextInterface; #[Package('fundamentals@after-sales')] @@ -30,6 +32,24 @@ public function getSourceIdentifier(array $data): string return $data['id']; } + protected function resolveLanguageId(string $locale, LanguageLookup $languageLookup, Context $context): ?string + { + $mapping = $this->mappingService->getMapping( + $this->migrationContext->getConnection()->getId(), + DefaultEntities::LANGUAGE, + $locale, + $context + ); + + if (isset($mapping['entityId'])) { + $this->mappingIds[] = $mapping['id']; + + return $mapping['entityId']; + } + + return $languageLookup->get($locale, $context); + } + /** * @param array $newData * @param array $sourceData diff --git a/src/Profile/Shopware/Gateway/Local/Reader/LanguageReader.php b/src/Profile/Shopware/Gateway/Local/Reader/LanguageReader.php index 8e4c2edf2..f1cff213a 100644 --- a/src/Profile/Shopware/Gateway/Local/Reader/LanguageReader.php +++ b/src/Profile/Shopware/Gateway/Local/Reader/LanguageReader.php @@ -40,7 +40,11 @@ public function supports(MigrationContextInterface $migrationContext): bool public function read(MigrationContextInterface $migrationContext): array { - $fetchedShopLocaleIds = \array_unique($this->fetchShopLocaleIds($migrationContext)); + $fetchedShopLocaleIds = \array_unique(\array_merge( + $this->fetchShopLocaleIds($migrationContext), + $this->fetchCustomerLocaleIds($migrationContext) + )); + $locales = $this->fetchLocales($fetchedShopLocaleIds, $migrationContext); return $this->appendAssociatedData($locales, $migrationContext); @@ -76,6 +80,21 @@ private function fetchShopLocaleIds(MigrationContextInterface $migrationContext) return $query->fetchFirstColumn(); } + /** + * @return list + */ + private function fetchCustomerLocaleIds(MigrationContextInterface $migrationContext): array + { + $connection = $this->getConnection($migrationContext); + $query = $connection->createQueryBuilder(); + $query->from('s_user', 'customer'); + $query->addSelect('customer.language'); + $query->distinct(); + $query->where('customer.language IS NOT NULL'); + + return $query->executeQuery()->fetchFirstColumn(); + } + private function fetchLocales(array $fetchedShopLocaleIds, MigrationContextInterface $migrationContext): array { $connection = $this->getConnection($migrationContext); diff --git a/tests/Profile/Shopware/Gateway/Local/LanguageReaderTest.php b/tests/Profile/Shopware/Gateway/Local/LanguageReaderTest.php index 4b8d1c35e..c0bd1d22b 100644 --- a/tests/Profile/Shopware/Gateway/Local/LanguageReaderTest.php +++ b/tests/Profile/Shopware/Gateway/Local/LanguageReaderTest.php @@ -7,6 +7,7 @@ namespace SwagMigrationAssistant\Test\Profile\Shopware\Gateway\Local; +use Doctrine\DBAL\Connection; use PHPUnit\Framework\TestCase; use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\MigrationContext; @@ -25,11 +26,18 @@ class LanguageReaderTest extends TestCase private MigrationContext $migrationContext; + private Connection $dbConnection; + + private int $customerLocaleId = 9999; + + private bool $customerLocaleInserted = false; + protected function setUp(): void { $this->connectionSetup(); - $this->languageReader = new LanguageReader(new ConnectionFactory()); + $connectionFactory = new ConnectionFactory(); + $this->languageReader = new LanguageReader($connectionFactory); $this->migrationContext = new MigrationContext( $this->connection, @@ -42,6 +50,25 @@ protected function setUp(): void ); $this->migrationContext->setGateway(new DummyLocalGateway()); + + $this->dbConnection = $connectionFactory->createDatabaseConnection($this->migrationContext); + $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [ + 'language' => 1, + 'id' => 1, + ]); + } + + protected function tearDown(): void + { + // reset customer to the fixture default locale + $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [ + 'language' => 1, + 'id' => 1, + ]); + + if ($this->customerLocaleInserted) { + $this->dbConnection->executeStatement('DELETE FROM s_core_locales WHERE id = :id', ['id' => $this->customerLocaleId]); + } } public function testRead(): void @@ -63,4 +90,38 @@ public function testRead(): void static::assertSame('de_DE', $data[1]['translations'][0]['locale']); static::assertSame('en_GB', $data[1]['translations'][1]['locale']); } + + public function testReadIncludesCustomerLocalesOutsideShopLocales(): void + { + $existingLocaleId = $this->dbConnection->fetchOne( + 'SELECT id FROM s_core_locales WHERE locale = :locale', + ['locale' => 'ar_EG'] + ); + + if ($existingLocaleId === false) { + $this->dbConnection->executeStatement( + 'INSERT INTO s_core_locales (id, locale, language, territory) VALUES (:id, :locale, :language, :territory)', + [ + 'id' => $this->customerLocaleId, + 'locale' => 'ar_EG', + 'language' => 'Arabic', + 'territory' => 'Egypt', + ] + ); + + $this->customerLocaleInserted = true; + } else { + $this->customerLocaleId = (int) $existingLocaleId; + } + + $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [ + 'language' => $this->customerLocaleId, + 'id' => 1, + ]); + + $data = $this->languageReader->read($this->migrationContext); + + static::assertCount(3, $data); + static::assertContains('ar-EG', \array_column($data, 'locale')); + } } diff --git a/tests/Profile/Shopware55/Converter/CustomerConverterTest.php b/tests/Profile/Shopware55/Converter/CustomerConverterTest.php index 82cbf5a28..f6d5ac967 100644 --- a/tests/Profile/Shopware55/Converter/CustomerConverterTest.php +++ b/tests/Profile/Shopware55/Converter/CustomerConverterTest.php @@ -372,4 +372,74 @@ public function testConvertBusinessCustomer(): void static::assertSame('Shopware AG', $converted['company']); static::assertSame(CustomerEntity::ACCOUNT_TYPE_BUSINESS, $converted['accountType']); } + + public function testConvertKeepsDefaultAddressesWhenAddressSalutationIsUnknown(): void + { + $customerData = require __DIR__ . '/../../../_fixtures/customer_data.php'; + $customerData = $customerData[0]; + $customerData['addresses'][0]['salutation'] = 'unknown-salutation'; + + $context = Context::createDefaultContext(); + $convertResult = $this->customerConverter->convert( + $customerData, + $context, + $this->migrationContext + ); + + $converted = $convertResult->getConverted(); + static::assertNotNull($converted); + static::assertCount(3, $converted['addresses']); + static::assertArrayNotHasKey('salutationId', $converted['addresses'][0]); + static::assertSame($converted['addresses'][0]['id'], $converted['defaultBillingAddressId']); + static::assertSame($converted['addresses'][1]['id'], $converted['defaultShippingAddressId']); + + $logs = $this->loggingService->getLoggingArray(); + static::assertCount(1, $logs); + static::assertSame(ConvertEntityUnknownLog::getCode(), $logs[0]['code']); + } + + public function testConvertUsesLanguageMappingWhenLanguageDoesNotExistYet(): void + { + $customerData = require __DIR__ . '/../../../_fixtures/customer_data.php'; + $customerData = $customerData[0]; + $customerData['customerlanguage']['locale'] = 'ar-EG'; + + $languageId = Uuid::randomHex(); + $context = Context::createDefaultContext(); + $this->mappingService->getOrCreateMapping( + $this->connectionId, + DefaultEntities::LANGUAGE, + 'ar-EG', + $context, + null, + [], + $languageId + ); + + $languageLookup = $this->createMock(LanguageLookup::class); + $languageLookup->expects($this->never())->method('get'); + + $validator = static::getContainer()->get('validator'); + $salesChannelRepo = static::getContainer()->get('sales_channel.repository'); + + $customerConverter = new Shopware55CustomerConverter( + $this->mappingService, + $this->loggingService, + $validator, + $salesChannelRepo, + static::getContainer()->get(CountryLookup::class), + $languageLookup, + static::getContainer()->get(CountryStateLookup::class), + ); + + $convertResult = $customerConverter->convert( + $customerData, + $context, + $this->migrationContext + ); + + $converted = $convertResult->getConverted(); + static::assertNotNull($converted); + static::assertSame($languageId, $converted['languageId']); + } }