From 887f7d85951077cafb0ce6fa79d8d25062478f53 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 10 Nov 2025 16:19:55 +0200 Subject: [PATCH 1/6] feat: delay step transition until all media is processed (#72) --- phpstan-baseline.neon | 15 - src/DependencyInjection/migration.xml | 20 +- .../Logging/Log/CannotGetFileRunLog.php | 9 +- .../Processor/HttpDownloadServiceBase.php | 5 +- .../Handler/ProcessMediaHandler.php | 131 ----- .../Processor/MediaProcessingProcessor.php | 200 +++++++- .../Message/ProcessMediaMessage.php | 73 --- .../Run/MigrationProgressFieldSerializer.php | 3 + .../Service/MediaFileProcessorService.php | 153 ------ .../MediaFileProcessorServiceInterface.php | 18 - .../Converter/ShippingMethodConverter.php | 1 + .../Shopware6/Converter/ProductConverter.php | 4 +- .../HttpOrderDocumentGenerationService.php | 4 +- .../swag-migration-result-screen.html.twig | 3 - .../src/module/swag-migration/snippet/de.json | 3 +- .../src/module/swag-migration/snippet/en.json | 3 +- .../Mapping/Lookup/TaxLookupTest.php | 2 +- .../Process/HttpDownloadServiceBaseTest.php | 2 +- .../MediaProcessingProcessorTest.php | 447 +++++++++++++++--- .../DummyMediaFileProcessorService.php | 24 - .../PropertyGroupOptionConverterTest.php | 7 + .../Converter/ShippingMethodConverterTest.php | 12 +- tests/acceptance/fixtures/AcceptanceTest.ts | 3 - .../fixtures/MediaProcessObserver.ts | 21 - .../tests/MigrationByUiFlow.spec.ts | 17 +- 25 files changed, 617 insertions(+), 563 deletions(-) delete mode 100644 src/Migration/MessageQueue/Handler/ProcessMediaHandler.php delete mode 100644 src/Migration/MessageQueue/Message/ProcessMediaMessage.php delete mode 100644 src/Migration/Service/MediaFileProcessorService.php delete mode 100644 src/Migration/Service/MediaFileProcessorServiceInterface.php delete mode 100644 tests/Mock/Migration/Service/DummyMediaFileProcessorService.php delete mode 100644 tests/acceptance/fixtures/MediaProcessObserver.ts diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1d01cf86e..a770c6376 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,15 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Property SwagMigrationAssistant\\Migration\\Converter\\Converter\:\:\$mainMapping \(array\{id\: string, connectionId\: string, oldIdentifier\: string\|null, entityUuid\: string\|null, entityValue\: string\|null, checksum\: string\|null, additionalData\: array\\|null\}\|null\) does not accept array\{additionalData\: non\-empty\-array\\}\|array\{id\: string, connectionId\: string, oldIdentifier\: string\|null, entityUuid\: string\|null, entityValue\: string\|null, checksum\: string\|null, additionalData\: non\-empty\-array\\}\.$#' - count: 1 - path: src/Migration/Converter/Converter.php - - - - message: '#^Property SwagMigrationAssistant\\Migration\\Converter\\Converter\:\:\$mainMapping \(array\{id\: string, connectionId\: string, oldIdentifier\: string\|null, entityUuid\: string\|null, entityValue\: string\|null, checksum\: string\|null, additionalData\: array\\|null\}\|null\) does not accept array\{checksum\: string\}\|array\{id\: string, connectionId\: string, oldIdentifier\: string\|null, entityUuid\: string\|null, entityValue\: string\|null, checksum\: string, additionalData\: array\\|null\}\.$#' - count: 1 - path: src/Migration/Converter/Converter.php - - message: "#^swag_migration_data\\.run association has a configured autoload\\=\\=\\=true, this is forbidden for platform integrations$#" count: 1 @@ -820,11 +810,6 @@ parameters: count: 1 path: src/Profile/Shopware6/Converter/NumberRangeConverter.php - - - message: "#^Method SwagMigrationAssistant\\\\Profile\\\\Shopware6\\\\Converter\\\\ProductConverter\\:\\:checkDefaultCurrency\\(\\) has parameter \\$source with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Profile/Shopware6/Converter/ProductConverter.php - - message: "#^Method SwagMigrationAssistant\\\\Profile\\\\Shopware6\\\\Converter\\\\PropertyGroupConverter\\:\\:convertOption\\(\\) has parameter \\$option with no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/DependencyInjection/migration.xml b/src/DependencyInjection/migration.xml index b67dc462e..0fb9337d4 100644 --- a/src/DependencyInjection/migration.xml +++ b/src/DependencyInjection/migration.xml @@ -234,13 +234,6 @@ - - - - - - - @@ -318,14 +311,6 @@ - - - - - - - - @@ -407,8 +392,11 @@ - + + + + diff --git a/src/Migration/Logging/Log/CannotGetFileRunLog.php b/src/Migration/Logging/Log/CannotGetFileRunLog.php index 56397ae6e..e66336cee 100644 --- a/src/Migration/Logging/Log/CannotGetFileRunLog.php +++ b/src/Migration/Logging/Log/CannotGetFileRunLog.php @@ -7,7 +7,6 @@ namespace SwagMigrationAssistant\Migration\Logging\Log; -use GuzzleHttp\Exception\RequestException; use Shopware\Core\Framework\Log\Package; #[Package('fundamentals@after-sales')] @@ -18,7 +17,7 @@ public function __construct( string $entity, string $sourceId, private readonly string $uri, - private readonly ?RequestException $requestException = null, + private readonly ?\Throwable $error = null, ) { parent::__construct($runId, $entity, $sourceId); } @@ -71,10 +70,10 @@ public function getDescription(): string $args['sourceId'] ); - if ($this->requestException !== null) { + if ($this->error !== null) { $description .= \sprintf( - ' The following request error occurred: %s', - $this->requestException->getMessage() + ' The following error occurred: %s', + $this->error->getMessage() ); } diff --git a/src/Migration/Media/Processor/HttpDownloadServiceBase.php b/src/Migration/Media/Processor/HttpDownloadServiceBase.php index 2f02e92d6..111781050 100644 --- a/src/Migration/Media/Processor/HttpDownloadServiceBase.php +++ b/src/Migration/Media/Processor/HttpDownloadServiceBase.php @@ -9,7 +9,6 @@ use Doctrine\DBAL\Connection; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Promise; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Promise\Utils; use Psr\Http\Message\ResponseInterface; @@ -30,7 +29,7 @@ use SwagMigrationAssistant\Migration\Media\MediaFileProcessorInterface; use SwagMigrationAssistant\Migration\Media\MediaProcessWorkloadStruct; use SwagMigrationAssistant\Migration\Media\SwagMigrationMediaFileCollection; -use SwagMigrationAssistant\Migration\MessageQueue\Handler\ProcessMediaHandler; +use SwagMigrationAssistant\Migration\MessageQueue\Handler\Processor\MediaProcessingProcessor; use SwagMigrationAssistant\Migration\MigrationContextInterface; /** @@ -114,7 +113,7 @@ function (MediaProcessWorkloadStruct $work) use ($uuid) { $work->setAdditionalData($additionalData); $work->setErrorCount($work->getErrorCount() + 1); - if ($work->getErrorCount() > ProcessMediaHandler::MEDIA_ERROR_THRESHOLD) { + if ($work->getErrorCount() > MediaProcessingProcessor::MEDIA_ERROR_THRESHOLD) { $failureUuids[] = $uuid; $work->setState(MediaProcessWorkloadStruct::ERROR_STATE); $this->loggingService->addLogEntry(new CannotGetFileRunLog( diff --git a/src/Migration/MessageQueue/Handler/ProcessMediaHandler.php b/src/Migration/MessageQueue/Handler/ProcessMediaHandler.php deleted file mode 100644 index 6839764c6..000000000 --- a/src/Migration/MessageQueue/Handler/ProcessMediaHandler.php +++ /dev/null @@ -1,131 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SwagMigrationAssistant\Migration\MessageQueue\Handler; - -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; -use Shopware\Core\Framework\Log\Package; -use SwagMigrationAssistant\Exception\MigrationException; -use SwagMigrationAssistant\Exception\NoConnectionFoundException; -use SwagMigrationAssistant\Migration\Logging\Log\ExceptionRunLog; -use SwagMigrationAssistant\Migration\Logging\Log\ProcessorNotFoundLog; -use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; -use SwagMigrationAssistant\Migration\Media\MediaFileProcessorInterface; -use SwagMigrationAssistant\Migration\Media\MediaFileProcessorRegistryInterface; -use SwagMigrationAssistant\Migration\Media\MediaProcessWorkloadStruct; -use SwagMigrationAssistant\Migration\MessageQueue\Message\ProcessMediaMessage; -use SwagMigrationAssistant\Migration\MigrationContextFactoryInterface; -use SwagMigrationAssistant\Migration\MigrationContextInterface; -use SwagMigrationAssistant\Migration\Run\SwagMigrationRunCollection; -use SwagMigrationAssistant\Migration\Run\SwagMigrationRunEntity; -use Symfony\Component\Messenger\Attribute\AsMessageHandler; - -/** - * @internal - */ -#[AsMessageHandler] -#[Package('fundamentals@after-sales')] -final class ProcessMediaHandler -{ - final public const MEDIA_ERROR_THRESHOLD = 3; - - /** - * @param EntityRepository $migrationRunRepo - */ - public function __construct( - private readonly EntityRepository $migrationRunRepo, - private readonly MediaFileProcessorRegistryInterface $mediaFileProcessorRegistry, - private readonly LoggingServiceInterface $loggingService, - private readonly MigrationContextFactoryInterface $migrationContextFactory, - ) { - } - - /** - * @throws MigrationException - */ - public function __invoke(ProcessMediaMessage $message): void - { - $context = $message->getContext(); - - $run = $this->migrationRunRepo->search(new Criteria([$message->getRunId()]), $context)->first(); - - if (!$run instanceof SwagMigrationRunEntity) { - throw MigrationException::entityNotExists(SwagMigrationRunEntity::class, $message->getRunId()); - } - - $connection = $run->getConnection(); - if ($connection === null) { - throw MigrationException::entityNotExists(SwagMigrationRunEntity::class, $message->getRunId()); - } - - $migrationContext = $this->migrationContextFactory->create($run, 0, 0, $message->getEntityName()); - - if ($migrationContext === null) { - throw MigrationException::entityNotExists(SwagMigrationRunEntity::class, $message->getRunId()); - } - - $workload = []; - foreach ($message->getMediaFileIds() as $mediaFileId) { - $workload[] = new MediaProcessWorkloadStruct( - $mediaFileId, - $message->getRunId(), - MediaProcessWorkloadStruct::IN_PROGRESS_STATE - ); - } - - try { - $processor = $this->mediaFileProcessorRegistry->getProcessor($migrationContext); - $workload = $processor->process($migrationContext, $context, $workload); - $this->processFailures($context, $migrationContext, $processor, $workload); - } catch (NoConnectionFoundException $e) { - $this->loggingService->addLogEntry(new ProcessorNotFoundLog( - $message->getRunId(), - $message->getEntityName(), - $connection->getProfileName(), - $connection->getGatewayName() - )); - - $this->loggingService->saveLogging($context); - } catch (\Exception $e) { - $this->loggingService->addLogEntry(new ExceptionRunLog( - $message->getRunId(), - $message->getEntityName(), - $e - )); - - $this->loggingService->saveLogging($context); - } - } - - /** - * @param MediaProcessWorkloadStruct[] $workload - */ - private function processFailures( - Context $context, - MigrationContextInterface $migrationContext, - MediaFileProcessorInterface $processor, - array $workload, - ): void { - for ($i = 0; $i < self::MEDIA_ERROR_THRESHOLD; ++$i) { - $errorWorkload = []; - - foreach ($workload as $item) { - if ($item->getErrorCount() > 0) { - $errorWorkload[] = $item; - } - } - - if (empty($errorWorkload)) { - break; - } - - $workload = $processor->process($migrationContext, $context, $errorWorkload); - } - } -} diff --git a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php index 2698a6a72..38968fcc2 100644 --- a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php +++ b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php @@ -7,10 +7,26 @@ namespace SwagMigrationAssistant\Migration\MessageQueue\Handler\Processor; +use Doctrine\DBAL\Connection; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter; use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Uuid\Uuid; +use SwagMigrationAssistant\Exception\DataSetNotFoundException; +use SwagMigrationAssistant\Exception\MigrationException; +use SwagMigrationAssistant\Exception\NoConnectionFoundException; use SwagMigrationAssistant\Migration\Data\SwagMigrationDataCollection; +use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSetRegistry; +use SwagMigrationAssistant\Migration\Logging\Log\DataSetNotFoundLog; +use SwagMigrationAssistant\Migration\Logging\Log\ExceptionRunLog; +use SwagMigrationAssistant\Migration\Logging\Log\ProcessorNotFoundLog; +use SwagMigrationAssistant\Migration\Logging\LoggingService; +use SwagMigrationAssistant\Migration\Media\MediaFileProcessorInterface; +use SwagMigrationAssistant\Migration\Media\MediaFileProcessorRegistryInterface; +use SwagMigrationAssistant\Migration\Media\MediaProcessWorkloadStruct; use SwagMigrationAssistant\Migration\Media\SwagMigrationMediaFileCollection; use SwagMigrationAssistant\Migration\MessageQueue\Message\MigrationProcessMessage; use SwagMigrationAssistant\Migration\MigrationContextInterface; @@ -19,12 +35,14 @@ use SwagMigrationAssistant\Migration\Run\RunTransitionServiceInterface; use SwagMigrationAssistant\Migration\Run\SwagMigrationRunCollection; use SwagMigrationAssistant\Migration\Run\SwagMigrationRunEntity; -use SwagMigrationAssistant\Migration\Service\MediaFileProcessorServiceInterface; use Symfony\Component\Messenger\MessageBusInterface; #[Package('fundamentals@after-sales')] class MediaProcessingProcessor extends AbstractProcessor { + final public const MEDIA_ERROR_THRESHOLD = 3; + final public const MESSAGE_SIZE = 10; + /** * @param EntityRepository $migrationRunRepo * @param EntityRepository $migrationDataRepo @@ -35,8 +53,11 @@ public function __construct( EntityRepository $migrationDataRepo, EntityRepository $migrationMediaFileRepo, RunTransitionServiceInterface $runTransitionService, - private readonly MediaFileProcessorServiceInterface $mediaFileProcessorService, private readonly MessageBusInterface $bus, + private readonly LoggingService $loggingService, + private readonly Connection $dbalConnection, + private readonly MediaFileProcessorRegistryInterface $mediaFileProcessorRegistry, + private readonly DataSetRegistry $dataSetRegistry, ) { parent::__construct( $migrationRunRepo, @@ -57,19 +78,182 @@ public function process( SwagMigrationRunEntity $run, MigrationProgress $progress, ): void { - $fileCount = $this->mediaFileProcessorService->processMediaFiles($migrationContext, $context); + $connection = $run->getConnection(); + if ($connection === null) { + throw MigrationException::entityNotExists(SwagMigrationRunEntity::class, $migrationContext->getRunUuid()); + } - if ($fileCount <= 0) { + $mediaFiles = $this->getMediaFiles($migrationContext); + if (empty($mediaFiles)) { $this->runTransitionService->transitionToRunStep($migrationContext->getRunUuid(), MigrationStep::CLEANUP); - $this->updateProgress($migrationContext->getRunUuid(), $progress, $context); $this->bus->dispatch(new MigrationProcessMessage($context, $migrationContext->getRunUuid())); return; } - $progress->setCurrentEntityProgress($progress->getCurrentEntityProgress() + $fileCount); - $progress->setProgress($progress->getProgress() + $fileCount); - $this->updateProgress($migrationContext->getRunUuid(), $progress, $context); + $currentDataSet = null; + $currentCount = 0; + $workload = []; + foreach ($mediaFiles as $mediaFile) { + if ($currentDataSet === null) { + try { + $currentDataSet = $this->dataSetRegistry->getDataSet($migrationContext, $mediaFile['entity']); + } catch (DataSetNotFoundException $e) { + $this->logDataSetNotFoundException($migrationContext, $mediaFile); + + continue; + } + } + + if ($currentDataSet::getEntity() !== $mediaFile['entity']) { + break; + } + + ++$currentCount; + + if ($currentCount > self::MESSAGE_SIZE) { + break; + } + + $workload[] = new MediaProcessWorkloadStruct( + $mediaFile['media_id'], + $run->getId(), + MediaProcessWorkloadStruct::IN_PROGRESS_STATE + ); + } + + \assert($currentDataSet !== null); + + try { + $processor = $this->mediaFileProcessorRegistry->getProcessor($migrationContext); + $workload = $processor->process($migrationContext, $context, $workload); + $this->processFailures($context, $migrationContext, $processor, $workload); + } catch (NoConnectionFoundException $e) { + $this->loggingService->addLogEntry(new ProcessorNotFoundLog( + $run->getId(), + $currentDataSet::getEntity(), + $connection->getProfileName(), + $connection->getGatewayName() + )); + + $this->loggingService->saveLogging($context); + } catch (\Throwable $e) { + $this->loggingService->addLogEntry(new ExceptionRunLog( + $run->getId(), + $currentDataSet::getEntity(), + $e + )); + + $this->loggingService->saveLogging($context); + } + + $this->loggingService->saveLogging($context); + + $progress->setCurrentEntityProgress($progress->getCurrentEntityProgress() + \count($workload)); + $progress->setProgress($progress->getProgress() + \count($workload)); + $this->updateProgress($run->getId(), $progress, $context); + + if ($this->isAllMediaProcessed($context, $migrationContext->getRunUuid())) { + $this->runTransitionService->transitionToRunStep($migrationContext->getRunUuid(), MigrationStep::CLEANUP); + } + $this->bus->dispatch(new MigrationProcessMessage($context, $migrationContext->getRunUuid())); } + + /** + * @return array> + */ + private function getMediaFiles(MigrationContextInterface $migrationContext): array + { + $queryBuilder = $this->dbalConnection->createQueryBuilder(); + + $result = $queryBuilder + ->select('*') + ->from('swag_migration_media_file') + ->where('run_id = :runId') + ->andWhere('written = 1') + ->orderBy('entity, file_size') + ->setFirstResult($migrationContext->getOffset()) + ->setMaxResults($migrationContext->getLimit()) + ->setParameter('runId', Uuid::fromHexToBytes($migrationContext->getRunUuid())) + ->executeQuery() + ->fetchAllAssociative(); + foreach ($result as &$media) { + $media['id'] = Uuid::fromBytesToHex($media['id']); + $media['run_id'] = Uuid::fromBytesToHex($media['run_id']); + $media['media_id'] = Uuid::fromBytesToHex($media['media_id']); + } + + return $result; + } + + /** + * @param MediaProcessWorkloadStruct[] $workload + */ + private function processFailures( + Context $context, + MigrationContextInterface $migrationContext, + MediaFileProcessorInterface $processor, + array $workload, + ): void { + for ($i = 0; $i < self::MEDIA_ERROR_THRESHOLD; ++$i) { + $errorWorkload = []; + + foreach ($workload as $item) { + if ($item->getErrorCount() > 0) { + $errorWorkload[] = $item; + } + } + + if (empty($errorWorkload)) { + break; + } + + $workload = $processor->process($migrationContext, $context, $errorWorkload); + } + } + + private function isAllMediaProcessed(Context $context, string $runId): bool + { + $criteria = new Criteria(); + $criteria->addFilter( + new EqualsFilter('runId', $runId) + ); + $criteria->addFilter( + new MultiFilter( + MultiFilter::CONNECTION_AND, + [ + new EqualsFilter('processed', false), + new EqualsFilter('processFailure', false), + ] + ) + ); + + $unprocessedCount = $this->migrationMediaFileRepo->search($criteria, $context)->getTotal(); + + return $unprocessedCount === 0; + } + + /** + * @param array $mediaFile + */ + private function logDataSetNotFoundException( + MigrationContextInterface $migrationContext, + array $mediaFile, + ): void { + $connection = $migrationContext->getConnection(); + + if ($connection === null) { + return; + } + + $this->loggingService->addLogEntry( + new DataSetNotFoundLog( + $migrationContext->getRunUuid(), + $mediaFile['entity'], + $mediaFile['id'], + $connection->getProfileName() + ) + ); + } } diff --git a/src/Migration/MessageQueue/Message/ProcessMediaMessage.php b/src/Migration/MessageQueue/Message/ProcessMediaMessage.php deleted file mode 100644 index 2bbdfa279..000000000 --- a/src/Migration/MessageQueue/Message/ProcessMediaMessage.php +++ /dev/null @@ -1,73 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SwagMigrationAssistant\Migration\MessageQueue\Message; - -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\Log\Package; -use Shopware\Core\Framework\MessageQueue\AsyncMessageInterface; - -#[Package('fundamentals@after-sales')] -class ProcessMediaMessage implements AsyncMessageInterface -{ - /** - * @param array $mediaFileIds - */ - public function __construct( - private array $mediaFileIds, - private string $runId, - private string $entityName, - private Context $context, - ) { - } - - public function getContext(): Context - { - return $this->context; - } - - public function setContext(Context $context): void - { - $this->context = $context; - } - - /** - * @param array $mediaFileIds - */ - public function setMediaFileIds(array $mediaFileIds): void - { - $this->mediaFileIds = $mediaFileIds; - } - - public function setRunId(string $runId): void - { - $this->runId = $runId; - } - - /** - * @return string[] - */ - public function getMediaFileIds(): array - { - return $this->mediaFileIds; - } - - public function getRunId(): string - { - return $this->runId; - } - - public function getEntityName(): string - { - return $this->entityName; - } - - public function setEntityName(string $entityName): void - { - $this->entityName = $entityName; - } -} diff --git a/src/Migration/Run/MigrationProgressFieldSerializer.php b/src/Migration/Run/MigrationProgressFieldSerializer.php index 3736d4416..d0b5b9cf7 100644 --- a/src/Migration/Run/MigrationProgressFieldSerializer.php +++ b/src/Migration/Run/MigrationProgressFieldSerializer.php @@ -44,12 +44,15 @@ public function encode( $dataSet = $dataSet->jsonSerialize(); } } + + unset($dataSet); } if (isset($value['dataSets']) && \is_array($value['dataSets'])) { foreach ($value['dataSets'] as &$dataSet) { unset($dataSet['extensions']); } + unset($dataSet); } $data = new KeyValuePair($data->getKey(), $value, $data->isRaw()); diff --git a/src/Migration/Service/MediaFileProcessorService.php b/src/Migration/Service/MediaFileProcessorService.php deleted file mode 100644 index 09ccc695b..000000000 --- a/src/Migration/Service/MediaFileProcessorService.php +++ /dev/null @@ -1,153 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SwagMigrationAssistant\Migration\Service; - -use Doctrine\DBAL\Connection; -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\Log\Package; -use Shopware\Core\Framework\Uuid\Uuid; -use SwagMigrationAssistant\Exception\DataSetNotFoundException; -use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSet; -use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSetRegistry; -use SwagMigrationAssistant\Migration\Logging\Log\DataSetNotFoundLog; -use SwagMigrationAssistant\Migration\Logging\LoggingService; -use SwagMigrationAssistant\Migration\MessageQueue\Message\ProcessMediaMessage; -use SwagMigrationAssistant\Migration\MigrationContextInterface; -use Symfony\Component\Messenger\MessageBusInterface; - -#[Package('fundamentals@after-sales')] -class MediaFileProcessorService implements MediaFileProcessorServiceInterface -{ - final public const MESSAGE_SIZE = 5; - - public function __construct( - private readonly MessageBusInterface $messageBus, - private readonly DataSetRegistry $dataSetRegistry, - private readonly LoggingService $loggingService, - private readonly Connection $dbalConnection, - ) { - } - - public function processMediaFiles(MigrationContextInterface $migrationContext, Context $context): int - { - $mediaFiles = $this->getMediaFiles($migrationContext); - - $currentDataSet = null; - $currentCount = 0; - $messageMediaUuids = []; - foreach ($mediaFiles as $mediaFile) { - if ($currentDataSet === null) { - try { - $currentDataSet = $this->dataSetRegistry->getDataSet($migrationContext, $mediaFile['entity']); - } catch (DataSetNotFoundException $e) { - $this->logDataSetNotFoundException($migrationContext, $mediaFile); - - continue; - } - } - - if ($currentDataSet::getEntity() !== $mediaFile['entity']) { - $this->addMessageToBus($migrationContext->getRunUuid(), $context, $currentDataSet, $messageMediaUuids); - - try { - $messageMediaUuids = []; - $currentCount = 0; - $currentDataSet = $this->dataSetRegistry->getDataSet($migrationContext, $mediaFile['entity']); - } catch (DataSetNotFoundException $e) { - $this->logDataSetNotFoundException($migrationContext, $mediaFile); - - continue; - } - } - - ++$currentCount; - $messageMediaUuids[] = $mediaFile['media_id']; - - if ($currentCount < self::MESSAGE_SIZE) { - continue; - } - - $this->addMessageToBus($migrationContext->getRunUuid(), $context, $currentDataSet, $messageMediaUuids); - $messageMediaUuids = []; - $currentCount = 0; - } - - if ($currentCount > 0 && $currentDataSet !== null) { - $this->addMessageToBus($migrationContext->getRunUuid(), $context, $currentDataSet, $messageMediaUuids); - } - - $this->loggingService->saveLogging($context); - - return \count($mediaFiles); - } - - /** - * @return array> - */ - private function getMediaFiles(MigrationContextInterface $migrationContext): array - { - $queryBuilder = $this->dbalConnection->createQueryBuilder(); - - $result = $queryBuilder - ->select('*') - ->from('swag_migration_media_file') - ->where('run_id = :runId') - ->andWhere('written = 1') - ->orderBy('entity, file_size') - ->setFirstResult($migrationContext->getOffset()) - ->setMaxResults($migrationContext->getLimit()) - ->setParameter('runId', Uuid::fromHexToBytes($migrationContext->getRunUuid())) - ->executeQuery() - ->fetchAllAssociative(); - foreach ($result as &$media) { - $media['id'] = Uuid::fromBytesToHex($media['id']); - $media['run_id'] = Uuid::fromBytesToHex($media['run_id']); - $media['media_id'] = Uuid::fromBytesToHex($media['media_id']); - } - - return $result; - } - - /** - * @param array $mediaUuids - */ - private function addMessageToBus(string $runUuid, Context $context, DataSet $dataSet, array $mediaUuids): void - { - $message = new ProcessMediaMessage( - $mediaUuids, - $runUuid, - $dataSet::getEntity(), - $context - ); - - $this->messageBus->dispatch($message); - } - - /** - * @param array $mediaFile - */ - private function logDataSetNotFoundException( - MigrationContextInterface $migrationContext, - array $mediaFile, - ): void { - $connection = $migrationContext->getConnection(); - - if ($connection === null) { - return; - } - - $this->loggingService->addLogEntry( - new DataSetNotFoundLog( - $migrationContext->getRunUuid(), - $mediaFile['entity'], - $mediaFile['id'], - $connection->getProfileName() - ) - ); - } -} diff --git a/src/Migration/Service/MediaFileProcessorServiceInterface.php b/src/Migration/Service/MediaFileProcessorServiceInterface.php deleted file mode 100644 index 17e2013cb..000000000 --- a/src/Migration/Service/MediaFileProcessorServiceInterface.php +++ /dev/null @@ -1,18 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SwagMigrationAssistant\Migration\Service; - -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\Log\Package; -use SwagMigrationAssistant\Migration\MigrationContextInterface; - -#[Package('fundamentals@after-sales')] -interface MediaFileProcessorServiceInterface -{ - public function processMediaFiles(MigrationContextInterface $migrationContext, Context $context): int; -} diff --git a/src/Profile/Shopware/Converter/ShippingMethodConverter.php b/src/Profile/Shopware/Converter/ShippingMethodConverter.php index 12954c92a..f24ad3af5 100644 --- a/src/Profile/Shopware/Converter/ShippingMethodConverter.php +++ b/src/Profile/Shopware/Converter/ShippingMethodConverter.php @@ -1060,6 +1060,7 @@ private function setOtherConditions( ], ]; + // @phpstan-ignore-next-line parameterByRef.type $mainOrContainer['children'][0]['children'][] = $condition; } } diff --git a/src/Profile/Shopware6/Converter/ProductConverter.php b/src/Profile/Shopware6/Converter/ProductConverter.php index aff6ef54b..60494ae22 100644 --- a/src/Profile/Shopware6/Converter/ProductConverter.php +++ b/src/Profile/Shopware6/Converter/ProductConverter.php @@ -147,7 +147,6 @@ protected function convertData(array $data): ConvertStruct DefaultEntities::PRODUCT ); - /** @phpstan-ignore-next-line */ $this->checkDefaultCurrency($setting, 'price'); } @@ -209,6 +208,9 @@ protected function convertData(array $data): ConvertStruct return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); } + /** + * @param array $source + */ private function checkDefaultCurrency(array &$source, string $key): void { // If the default currency of source and destination is identically, there is no need to add a default price diff --git a/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php b/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php index b49ae8d10..e7551f23e 100644 --- a/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php +++ b/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php @@ -28,7 +28,7 @@ use SwagMigrationAssistant\Migration\Media\MediaProcessWorkloadStruct; use SwagMigrationAssistant\Migration\Media\Processor\BaseMediaService; use SwagMigrationAssistant\Migration\Media\SwagMigrationMediaFileCollection; -use SwagMigrationAssistant\Migration\MessageQueue\Handler\ProcessMediaHandler; +use SwagMigrationAssistant\Migration\MessageQueue\Handler\Processor\MediaProcessingProcessor; use SwagMigrationAssistant\Migration\MigrationContextInterface; use SwagMigrationAssistant\Profile\Shopware\Gateway\Api\ShopwareApiGateway; use SwagMigrationAssistant\Profile\Shopware6\Gateway\Connection\ConnectionFactoryInterface; @@ -277,7 +277,7 @@ private function handleFailedRequest( $mappedWorkload->setAdditionalData($additionalData); $mappedWorkload->setErrorCount($mappedWorkload->getErrorCount() + 1); - if ($mappedWorkload->getErrorCount() > ProcessMediaHandler::MEDIA_ERROR_THRESHOLD) { + if ($mappedWorkload->getErrorCount() > MediaProcessingProcessor::MEDIA_ERROR_THRESHOLD) { $failureUuids[] = $uuid; $mappedWorkload->setState(MediaProcessWorkloadStruct::ERROR_STATE); $this->loggingService->addLogEntry(new CannotGetFileRunLog( diff --git a/src/Resources/app/administration/src/module/swag-migration/component/loading-screen/swag-migration-result-screen/swag-migration-result-screen.html.twig b/src/Resources/app/administration/src/module/swag-migration/component/loading-screen/swag-migration-result-screen/swag-migration-result-screen.html.twig index 460e41e09..c7ab4febc 100644 --- a/src/Resources/app/administration/src/module/swag-migration/component/loading-screen/swag-migration-result-screen/swag-migration-result-screen.html.twig +++ b/src/Resources/app/administration/src/module/swag-migration/component/loading-screen/swag-migration-result-screen/swag-migration-result-screen.html.twig @@ -21,9 +21,6 @@
{{ $tc('swag-migration.index.loadingScreenCard.result.title') }}
-
- {{ $tc('swag-migration.index.loadingScreenCard.result.caption') }} -
{% endblock %} {% endblock %} diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json index b17f020bb..7361dfc3c 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json @@ -426,9 +426,8 @@ }, "result": { "title": "Der Migrations-Assistent ist fertig", - "caption": "Die Medien werden weiter im Hintergrund heruntergeladen. Große Dateien können etwas Zeit in Anspruch nehmen.", "logSummary": "Logbuch", - "historyHint": "Du kannst diese Informationen jederzeit in der Migrations-Historie einsehen. Es kann sein, dass zu einem späteren Zeitpunkt noch mehr Log-Nachrichten hinzukommen, weil der Mediendownload noch im Hintergrund läuft." + "historyHint": "Du kannst diese Informationen jederzeit in der Migrations-Historie einsehen." } }, "confirmAbortDialog": { diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json index 00f616c8f..82a3e2490 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json @@ -426,9 +426,8 @@ }, "result": { "title": "The Migration Assistant is done", - "caption": "The media download continues in the background. Large files can take some time.", "logSummary": "Logbook", - "historyHint": "You can view this information any time in the migration history. It is possible that more log messages will be added at a later time because the media download is still running in the background." + "historyHint": "You can view this information any time in the migration history." } }, "confirmAbortDialog": { diff --git a/tests/Migration/Mapping/Lookup/TaxLookupTest.php b/tests/Migration/Mapping/Lookup/TaxLookupTest.php index 2c58a2b84..b5554fe4a 100644 --- a/tests/Migration/Mapping/Lookup/TaxLookupTest.php +++ b/tests/Migration/Mapping/Lookup/TaxLookupTest.php @@ -217,7 +217,7 @@ private function getMockedTaxLookup(): TaxLookup $cacheData = []; $taxRateCache = []; foreach ($databaseData as $data) { - $cacheData[$data['taxRate']] = $data['expectedResult']; + $cacheData[(string) $data['taxRate']] = $data['expectedResult']; $cacheData[$data['taxRate'] . '-' . $data['name']] = $data['expectedResult']; $taxRateCache[$data['expectedResult']] = $data['taxRate']; diff --git a/tests/Migration/Media/Process/HttpDownloadServiceBaseTest.php b/tests/Migration/Media/Process/HttpDownloadServiceBaseTest.php index 6638c42d8..728e78aed 100644 --- a/tests/Migration/Media/Process/HttpDownloadServiceBaseTest.php +++ b/tests/Migration/Media/Process/HttpDownloadServiceBaseTest.php @@ -198,7 +198,7 @@ public function testProcessWithRequestFailure(): void 'level' => 'warning', 'code' => 'SWAG_MIGRATION_CANNOT_GET_MEDIA_FILE', 'title' => 'The media file cannot be downloaded / copied', - 'description' => 'The media file with the uri "' . $mediaFiles[0]['uri'] . '" and media id "' . $mediaFiles[0]['mediaId'] . '" cannot be downloaded / copied. The following request error occurred: Request failed', + 'description' => 'The media file with the uri "' . $mediaFiles[0]['uri'] . '" and media id "' . $mediaFiles[0]['mediaId'] . '" cannot be downloaded / copied. The following error occurred: Request failed', 'parameters' => [ 'entity' => 'media', 'sourceId' => $mediaFiles[0]['mediaId'], diff --git a/tests/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessorTest.php b/tests/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessorTest.php index 602d994f1..ebec83667 100644 --- a/tests/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessorTest.php +++ b/tests/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessorTest.php @@ -7,13 +7,30 @@ namespace SwagMigrationAssistant\Test\Migration\MessageQueue\Handler\Processor; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Result; use PHPUnit\Framework\TestCase; use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Test\Stub\MessageBus\CollectingMessageBus; +use SwagMigrationAssistant\Exception\DataSetNotFoundException; +use SwagMigrationAssistant\Exception\MigrationException; +use SwagMigrationAssistant\Exception\NoConnectionFoundException; use SwagMigrationAssistant\Migration\Connection\SwagMigrationConnectionEntity; +use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSetRegistry; +use SwagMigrationAssistant\Migration\Logging\Log\DataSetNotFoundLog; +use SwagMigrationAssistant\Migration\Logging\Log\ProcessorNotFoundLog; +use SwagMigrationAssistant\Migration\Logging\LoggingService; +use SwagMigrationAssistant\Migration\Media\MediaFileProcessorInterface; +use SwagMigrationAssistant\Migration\Media\MediaFileProcessorRegistryInterface; +use SwagMigrationAssistant\Migration\Media\MediaProcessWorkloadStruct; +use SwagMigrationAssistant\Migration\Media\SwagMigrationMediaFileEntity; use SwagMigrationAssistant\Migration\MessageQueue\Handler\Processor\MediaProcessingProcessor; use SwagMigrationAssistant\Migration\MigrationContext; use SwagMigrationAssistant\Migration\Run\MigrationProgress; @@ -22,11 +39,9 @@ use SwagMigrationAssistant\Migration\Run\ProgressDataSetCollection; use SwagMigrationAssistant\Migration\Run\RunTransitionServiceInterface; use SwagMigrationAssistant\Migration\Run\SwagMigrationRunEntity; -use SwagMigrationAssistant\Migration\Service\MediaFileProcessorService; +use SwagMigrationAssistant\Profile\Shopware\DataSelection\DataSet\MediaDataSet; use SwagMigrationAssistant\Profile\Shopware55\Shopware55Profile; -use function PHPUnit\Framework\once; - #[Package('fundamentals@after-sales')] class MediaProcessingProcessorTest extends TestCase { @@ -34,116 +49,422 @@ class MediaProcessingProcessorTest extends TestCase private CollectingMessageBus $bus; + private MigrationContext $migrationContext; + + private SwagMigrationRunEntity $runEntity; + + private MigrationProgress $progress; + + /** + * @var array> + */ + private array $mediaFiles = []; + + private Connection $dbalConnection; + protected function setUp(): void { $this->bus = new CollectingMessageBus(); - $this->processor = new MediaProcessingProcessor( - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(EntityRepository::class), - $this->createMock(RunTransitionServiceInterface::class), - $this->createMock(MediaFileProcessorService::class), - $this->bus - ); - } - public function testProcessingWithoutMediaFiles(): void - { - $progress = new MigrationProgress( + $this->progress = new MigrationProgress( 0, 0, new ProgressDataSetCollection([ - 'product' => new ProgressDataSet('product', 1000), + 'media' => new ProgressDataSet('media', 1000), ]), - 'product', + 'media', 100 ); - $run = new SwagMigrationRunEntity(); - $run->setId(Uuid::randomHex()); - $run->setProgress($progress); - $run->setStep(MigrationStep::FETCHING); - $connection = new SwagMigrationConnectionEntity(); $connection->setId(Uuid::randomHex()); - $migrationContext = new MigrationContext(new Shopware55Profile(), $connection, $run->getId()); + $this->runEntity = new SwagMigrationRunEntity(); + $this->runEntity->setId(Uuid::randomHex()); + $this->runEntity->setProgress($this->progress); + $this->runEntity->setStep(MigrationStep::FETCHING); + $this->runEntity->setConnection($connection); + $this->migrationContext = new MigrationContext(new Shopware55Profile(), $connection, $this->runEntity->getId()); + + $result = $this->createMock(Result::class); + $result->method('fetchAllAssociative')->willReturnCallback(fn () => $this->mediaFiles); + + $queryBuilder = $this->createMock(QueryBuilder::class); + $queryBuilder->method('select')->willReturnSelf(); + $queryBuilder->method('from')->willReturnSelf(); + $queryBuilder->method('where')->willReturnSelf(); + $queryBuilder->method('andWhere')->willReturnSelf(); + $queryBuilder->method('orderBy')->willReturnSelf(); + $queryBuilder->method('setFirstResult')->willReturnSelf(); + $queryBuilder->method('setMaxResults')->willReturnSelf(); + $queryBuilder->method('setParameter')->willReturnSelf(); + $queryBuilder->method('executeQuery')->willReturn($result); + + $this->dbalConnection = $this->createMock(Connection::class); + $this->dbalConnection->method('createQueryBuilder')->willReturn($queryBuilder); + + $this->processor = new MediaProcessingProcessor( + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(RunTransitionServiceInterface::class), + $this->bus, + $this->createMock(LoggingService::class), + $this->dbalConnection, + $this->createMock(MediaFileProcessorRegistryInterface::class), + $this->createMock(DataSetRegistry::class), + ); + } + + public function testThrowsExceptionIfNoConnectionIsSet(): void + { + $this->runEntity = new SwagMigrationRunEntity(); + + try { + $this->processor->process( + $this->migrationContext, + Context::createDefaultContext(), + $this->runEntity, + $this->progress + ); + } catch (\Exception $e) { + static::assertInstanceOf(MigrationException::class, $e); + static::assertSame(MigrationException::ENTITY_NOT_EXISTS, $e->getErrorCode()); + } + } + + public function testTransitionsToNextStepIfNoMediaFiles(): void + { $runTransitionService = $this->createMock(RunTransitionServiceInterface::class); - $runTransitionService - ->expects(once()) + $runTransitionService->expects(static::once()) ->method('transitionToRunStep') - ->with($run->getId(), MigrationStep::CLEANUP); + ->with( + $this->migrationContext->getRunUuid(), + MigrationStep::CLEANUP + ); $this->processor = new MediaProcessingProcessor( $this->createMock(EntityRepository::class), $this->createMock(EntityRepository::class), $this->createMock(EntityRepository::class), $runTransitionService, - $this->createMock(MediaFileProcessorService::class), - $this->bus + $this->bus, + $this->createMock(LoggingService::class), + $this->createMock(Connection::class), + $this->createMock(MediaFileProcessorRegistryInterface::class), + $this->createMock(DataSetRegistry::class), ); $this->processor->process( - $migrationContext, + $this->migrationContext, Context::createDefaultContext(), - $run, - $progress + $this->runEntity, + $this->progress ); static::assertCount(1, $this->bus->getMessages()); } - public function testProcessing(): void + public function testHandlesDataSetNotFoundExceptionGracefully(): void { - $progress = new MigrationProgress( - 0, - 0, - new ProgressDataSetCollection([ - 'product' => new ProgressDataSet('product', 1000), - ]), - 'product', - 100 + $this->mediaFiles = [ + [ + 'id' => Uuid::randomBytes(), + 'run_id' => Uuid::randomBytes(), + 'media_id' => Uuid::randomBytes(), + 'entity' => 'media', + 'written' => 1, + 'file_size' => 10, + ], + ]; + + $dataSetRegistry = $this->createMock(DataSetRegistry::class); + $dataSetRegistry->method('getDataSet')->willThrowException( + new DataSetNotFoundException(400, MigrationException::DATASET_NOT_FOUND, 'unknown') ); - $run = new SwagMigrationRunEntity(); - $run->setId(Uuid::randomHex()); - $run->setProgress($progress); - $run->setStep(MigrationStep::FETCHING); + $logging = $this->createMock(LoggingService::class); + $logging->expects(static::once())->method('addLogEntry')->with( + static::isInstanceOf(DataSetNotFoundLog::class) + ); - $connection = new SwagMigrationConnectionEntity(); - $connection->setId(Uuid::randomHex()); + $processor = new MediaProcessingProcessor( + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(RunTransitionServiceInterface::class), + $this->bus, + $logging, + $this->dbalConnection, + $this->createMock(MediaFileProcessorRegistryInterface::class), + $dataSetRegistry + ); + + $processor->process( + $this->migrationContext, + Context::createDefaultContext(), + $this->runEntity, + $this->progress + ); + + static::assertCount(1, $this->bus->getMessages()); + } + + public function testHandlesNoConnectionFoundException(): void + { + $processorMock = $this->createMock(MediaFileProcessorInterface::class); + $processorMock->method('process')->willThrowException( + new NoConnectionFoundException(400, MigrationException::DATASET_NOT_FOUND, 'unknown') + ); + + $registry = $this->createMock(MediaFileProcessorRegistryInterface::class); + $registry->method('getProcessor')->willReturn($processorMock); + + $logging = $this->createMock(LoggingService::class); + $logging->expects(static::once())->method('addLogEntry')->with( + static::isInstanceOf(ProcessorNotFoundLog::class) + ); + + $this->mediaFiles = [ + [ + 'id' => Uuid::randomBytes(), + 'run_id' => Uuid::randomBytes(), + 'media_id' => Uuid::randomBytes(), + 'entity' => 'media', + 'written' => 1, + 'file_size' => 10, + ], + ]; - $migrationContext = new MigrationContext(new Shopware55Profile(), $connection, $run->getId()); + $dataSetRegistry = $this->createMock(DataSetRegistry::class); + $dataSetRegistry->method('getDataSet')->willReturn(new MediaDataSet()); + + $processor = new MediaProcessingProcessor( + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(RunTransitionServiceInterface::class), + $this->bus, + $logging, + $this->dbalConnection, + $registry, + $dataSetRegistry + ); + + $processor->process( + $this->migrationContext, + Context::createDefaultContext(), + $this->runEntity, + $this->progress + ); + + static::assertCount(1, $this->bus->getMessages()); + } + + public function testProcess(): void + { + $processorMock = $this->createMock(MediaFileProcessorInterface::class); + + $workload = [ + new MediaProcessWorkloadStruct( + Uuid::randomHex(), + Uuid::randomHex(), + MediaProcessWorkloadStruct::IN_PROGRESS_STATE, + [], + 0 + ), + ]; + + $processorMock->expects(static::once()) + ->method('process') + ->willReturn($workload); + + $processorRegistry = $this->createMock(MediaFileProcessorRegistryInterface::class); + $processorRegistry->method('getProcessor')->willReturn($processorMock); + + $this->mediaFiles = [ + [ + 'id' => Uuid::randomBytes(), + 'run_id' => Uuid::randomBytes(), + 'media_id' => Uuid::randomBytes(), + 'entity' => 'media', + 'written' => 1, + 'file_size' => 10, + ], + ]; + + $dataSetRegistry = $this->createMock(DataSetRegistry::class); + $dataSetRegistry->method('getDataSet')->willReturn(new MediaDataSet()); + + $processor = new MediaProcessingProcessor( + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(RunTransitionServiceInterface::class), + $this->bus, + $this->createMock(LoggingService::class), + $this->dbalConnection, + $processorRegistry, + $dataSetRegistry + ); + + $processor->process( + $this->migrationContext, + Context::createDefaultContext(), + $this->runEntity, + $this->progress + ); + + static::assertSame(1, $this->progress->getProgress()); + static::assertSame(101, $this->progress->getCurrentEntityProgress()); + } + + public function testProcessRetriesUntilNoErrors(): void + { + $processorMock = $this->createMock(MediaFileProcessorInterface::class); + + // First call returns workload with errorCount 1 + // Second call returns workload with errorCount 0 + $firstWorkload = [ + new MediaProcessWorkloadStruct( + Uuid::randomHex(), + Uuid::randomHex(), + MediaProcessWorkloadStruct::IN_PROGRESS_STATE, + [], + 1 + ), + ]; + $secondWorkload = [ + new MediaProcessWorkloadStruct( + Uuid::randomHex(), + Uuid::randomHex(), + MediaProcessWorkloadStruct::IN_PROGRESS_STATE, + [], + 0 + ), + ]; + + $processorMock->expects(static::exactly(2)) + ->method('process') + ->willReturnOnConsecutiveCalls($firstWorkload, $secondWorkload); + + $processorRegistry = $this->createMock(MediaFileProcessorRegistryInterface::class); + $processorRegistry->method('getProcessor')->willReturn($processorMock); + + $this->mediaFiles = [ + [ + 'id' => Uuid::randomBytes(), + 'run_id' => Uuid::randomBytes(), + 'media_id' => Uuid::randomBytes(), + 'entity' => 'media', + 'written' => 1, + 'file_size' => 10, + ], + ]; + + $dataSetRegistry = $this->createMock(DataSetRegistry::class); + $dataSetRegistry->method('getDataSet')->willReturn(new MediaDataSet()); + + $processor = new MediaProcessingProcessor( + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(EntityRepository::class), + $this->createMock(RunTransitionServiceInterface::class), + $this->bus, + $this->createMock(LoggingService::class), + $this->dbalConnection, + $processorRegistry, + $dataSetRegistry + ); + + $processor->process( + $this->migrationContext, + Context::createDefaultContext(), + $this->runEntity, + $this->progress + ); + + static::assertSame(1, $this->progress->getProgress()); + static::assertSame(101, $this->progress->getCurrentEntityProgress()); + } + + public function testTransitionsIfAllMediaIsProcessed(): void + { + $processorMock = $this->createMock(MediaFileProcessorInterface::class); + + $workload = [ + new MediaProcessWorkloadStruct( + Uuid::randomHex(), + Uuid::randomHex(), + MediaProcessWorkloadStruct::FINISH_STATE, + [], + 0 + ), + ]; + + $processorMock->expects(static::once()) + ->method('process') + ->willReturn($workload); + + $processorRegistry = $this->createMock(MediaFileProcessorRegistryInterface::class); + $processorRegistry->method('getProcessor')->willReturn($processorMock); + + $this->mediaFiles = [ + [ + 'id' => Uuid::randomBytes(), + 'run_id' => Uuid::randomBytes(), + 'media_id' => Uuid::randomBytes(), + 'entity' => 'media', + 'written' => 1, + 'file_size' => 10, + ], + ]; + + $dataSetRegistry = $this->createMock(DataSetRegistry::class); + $dataSetRegistry->method('getDataSet')->willReturn(new MediaDataSet()); $runTransitionService = $this->createMock(RunTransitionServiceInterface::class); - $runTransitionService - ->expects(static::never()) + $runTransitionService->expects(static::once()) ->method('transitionToRunStep') - ->with($run->getId(), MigrationStep::CLEANUP); + ->with( + $this->migrationContext->getRunUuid(), + MigrationStep::CLEANUP + ); - $mediaFileProcessorService = $this->createMock(MediaFileProcessorService::class); - $mediaFileProcessorService - ->expects(static::once()) - ->method('processMediaFiles') - ->willReturn(100); + $migrationMediaFileRepository = $this->createMock(EntityRepository::class); + $migrationMediaFileRepository->method('search')->willReturn( + new EntitySearchResult( + SwagMigrationMediaFileEntity::class, + 0, + new EntityCollection(), + null, + new Criteria(), + Context::createDefaultContext() + ) + ); - $this->processor = new MediaProcessingProcessor( - $this->createMock(EntityRepository::class), + $processor = new MediaProcessingProcessor( $this->createMock(EntityRepository::class), $this->createMock(EntityRepository::class), + $migrationMediaFileRepository, $runTransitionService, - $mediaFileProcessorService, - $this->bus + $this->bus, + $this->createMock(LoggingService::class), + $this->dbalConnection, + $processorRegistry, + $dataSetRegistry ); - $this->processor->process( - $migrationContext, + $processor->process( + $this->migrationContext, Context::createDefaultContext(), - $run, - $progress + $this->runEntity, + $this->progress ); static::assertCount(1, $this->bus->getMessages()); + static::assertSame(1, $this->progress->getProgress()); + static::assertSame(101, $this->progress->getCurrentEntityProgress()); } } diff --git a/tests/Mock/Migration/Service/DummyMediaFileProcessorService.php b/tests/Mock/Migration/Service/DummyMediaFileProcessorService.php deleted file mode 100644 index 602df5d03..000000000 --- a/tests/Mock/Migration/Service/DummyMediaFileProcessorService.php +++ /dev/null @@ -1,24 +0,0 @@ - - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SwagMigrationAssistant\Test\Mock\Migration\Service; - -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\Log\Package; -use SwagMigrationAssistant\Migration\MigrationContextInterface; -use SwagMigrationAssistant\Migration\Service\MediaFileProcessorService; - -#[Package('fundamentals@after-sales')] -class DummyMediaFileProcessorService extends MediaFileProcessorService -{ - public function processMediaFiles( - MigrationContextInterface $migrationContext, - Context $context, - ): int { - return 0; - } -} diff --git a/tests/Profile/Shopware55/Converter/PropertyGroupOptionConverterTest.php b/tests/Profile/Shopware55/Converter/PropertyGroupOptionConverterTest.php index 0361cdcf2..0395c6159 100644 --- a/tests/Profile/Shopware55/Converter/PropertyGroupOptionConverterTest.php +++ b/tests/Profile/Shopware55/Converter/PropertyGroupOptionConverterTest.php @@ -182,6 +182,8 @@ public function testConvertWithPropertiesAndProductConfigurators(): void ++$iterator; } + unset($relation); + $iterator = 0; foreach ($propertyRelationData as &$relation) { $relation['productId'] = $productData[5]['detail']['articleID']; @@ -198,6 +200,8 @@ public function testConvertWithPropertiesAndProductConfigurators(): void ++$iterator; } + + unset($relation); } public function testConvertWithPropertiesAndProductConfiguratorsAndOldIdentifier(): void @@ -261,6 +265,7 @@ public function testConvertWithPropertiesAndProductConfiguratorsAndOldIdentifier ++$iterator; } + unset($relation); $iterator = 0; foreach ($propertyRelationData as &$relation) { @@ -278,5 +283,7 @@ public function testConvertWithPropertiesAndProductConfiguratorsAndOldIdentifier ++$iterator; } + + unset($relation); } } diff --git a/tests/Profile/Shopware6/Converter/ShippingMethodConverterTest.php b/tests/Profile/Shopware6/Converter/ShippingMethodConverterTest.php index b96389a2c..4bbb14492 100644 --- a/tests/Profile/Shopware6/Converter/ShippingMethodConverterTest.php +++ b/tests/Profile/Shopware6/Converter/ShippingMethodConverterTest.php @@ -32,14 +32,18 @@ protected function createConverter( MediaFileServiceInterface $mediaFileService, ?array $mappingArray = [], ): ConverterInterface { + $uuid = Uuid::randomHex(); + /** @var StaticEntityRepository $shippingMethodRepository */ $shippingMethodRepository = new StaticEntityRepository([ new IdSearchResult( 1, // trigger already existing technical name check - [[ - 'primaryKey' => Uuid::randomHex(), - 'data' => [], - ]], + [ + $uuid => [ + 'primaryKey' => $uuid, + 'data' => [], + ], + ], new Criteria(), Context::createDefaultContext() ), diff --git a/tests/acceptance/fixtures/AcceptanceTest.ts b/tests/acceptance/fixtures/AcceptanceTest.ts index 75c7fad03..5fd9607d3 100644 --- a/tests/acceptance/fixtures/AcceptanceTest.ts +++ b/tests/acceptance/fixtures/AcceptanceTest.ts @@ -4,7 +4,6 @@ import type { FixtureTypes as BaseTypes } from '@shopware-ag/acceptance-test-sui import { MigrationUser } from './MigrationUser'; import { DatabaseCredentials, DatabaseCredentialsStruct } from './DatabaseCredentials'; import {EntityCounter, EntityCounterStruct} from './EntityCounter'; -import {MediaProcessObserver, MediaProcessObserverStruct} from './MediaProcessObserver'; export * from '@shopware-ag/acceptance-test-suite'; @@ -12,7 +11,6 @@ export interface MigrationFixtureTypes { MigrationUser: FixtureTypes['ShopAdmin'], DatabaseCredentials: DatabaseCredentialsStruct, EntityCounter: EntityCounterStruct, - MediaProcessObserver: MediaProcessObserverStruct, } export type FixtureTypes = MigrationFixtureTypes & BaseTypes; @@ -22,5 +20,4 @@ export const test = mergeTests( MigrationUser, DatabaseCredentials, EntityCounter, - MediaProcessObserver, ); diff --git a/tests/acceptance/fixtures/MediaProcessObserver.ts b/tests/acceptance/fixtures/MediaProcessObserver.ts deleted file mode 100644 index ff7f1012f..000000000 --- a/tests/acceptance/fixtures/MediaProcessObserver.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {test as base, expect } from '@playwright/test'; -import {FixtureTypes} from './AcceptanceTest'; - -export interface MediaProcessObserverStruct { - isMediaProcessing: () => Promise -} - -// ToDo MIG-985: remove this workaround when the underlying issue is fixed -export const MediaProcessObserver = base.extend({ - MediaProcessObserver: async ({ AdminApiContext }, use) => { - const isMediaProcessing = async () => { - const response = await AdminApiContext.get(`/api/_action/migration/is-media-processing`, {}); - expect(response.ok()).toBeTruthy(); - return await response.json(); - }; - - await use({ - isMediaProcessing, - }); - }, -}); diff --git a/tests/acceptance/tests/MigrationByUiFlow.spec.ts b/tests/acceptance/tests/MigrationByUiFlow.spec.ts index 506c52414..c3f035e69 100644 --- a/tests/acceptance/tests/MigrationByUiFlow.spec.ts +++ b/tests/acceptance/tests/MigrationByUiFlow.spec.ts @@ -12,7 +12,6 @@ test('As a shop owner I want to migrate my data from my old SW5 shop to SW6 via MigrationUser, DatabaseCredentials, EntityCounter, - MediaProcessObserver, }) => { const page = MigrationUser.page; await page.goto('/admin'); @@ -102,17 +101,6 @@ test('As a shop owner I want to migrate my data from my old SW5 shop to SW6 via await page.getByRole('button', { name: 'Back to overview' }).click(); }); - // ToDo MIG-985: Remove this if the underlying issue is fixed - await test.step('Wait for media download to finish', async () => { - await expect.poll(async () => { - return await MediaProcessObserver.isMediaProcessing(); - }, { - // Probe after 100ms and then every second - intervals: [100, 1_000], - timeout: 300_000, - }).toBe(false); - }); - await test.step('Expect entities to be there', async () => { await EntityCounter.checkEntityCount('swag_migration_logging', 699); @@ -127,8 +115,9 @@ test('As a shop owner I want to migrate my data from my old SW5 shop to SW6 via await EntityCounter.checkEntityCount('customer', 3); await EntityCounter.checkEntityCount('cms_page', 10); - await EntityCounter.checkEntityCount('media', 603); - await EntityCounter.checkEntityCount('media_folder', 26); + await EntityCounter.checkEntityCount('media', 595); + await EntityCounter.checkEntityCount('media_folder', 24); + await EntityCounter.checkEntityCount('document', 8); await EntityCounter.checkEntityCount('newsletter_recipient', 0); await EntityCounter.checkEntityCount('promotion', 4); From b0918be6a668e19793712d622e2b75a431f32222 Mon Sep 17 00:00:00 2001 From: Lars Kemper Date: Mon, 24 Nov 2025 08:00:20 +0100 Subject: [PATCH 2/6] docs: add pr template (#89) --- .github/PULL_REQUEST_TEMPLATE.md | 25 +++ .gitlab-ci.yml | 199 ------------------ .gitlab/Dockerfile | 34 --- .gitlab/install_test_data.php | 55 ----- .gitlab/post_install_migration_assistant.sh | 5 - .../tests/MigrationByUiFlow.spec.ts | 2 +- 6 files changed, 26 insertions(+), 294 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .gitlab-ci.yml delete mode 100644 .gitlab/Dockerfile delete mode 100644 .gitlab/install_test_data.php delete mode 100755 .gitlab/post_install_migration_assistant.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..056169d44 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +### 1. Why is this change necessary? + +// Please link to the relevant issues, if they exists, or explain the solved issue here. e.g. +// closes https://github.com/shopware/shopware/issues/{ISSUE_NUMBER} + +### 2. What does this change do, exactly? + +// Shortly explain how your change solves the issue +// If the issue has more than one obvious solution, +// shortly explain your reasoning for your chosen approach + +### 3. Describe each step to reproduce the issue or behaviour. + + +### 5. Checklist + +- [ ] I have created the PR in draft status and only open it when it's ready for review +- [ ] If the change is large: I have thought about splitting it up into smaller PRs +- [ ] I have written tests and if it's a bug fix verified that they fail without my change or that new code is covered by tests +- [ ] I have updated developer-facing release notes if this change affects external developers +- [ ] I have written or adjusted the documentation according to my changes +- [ ] I added the correct package annotation to each `src` file (not strictly necessary for tests) +- [ ] This change has code comments where appropriate, especially for non-obvious lines of code +- [ ] Ensure the pull request title as well as first commit follows conventional commits +- [ ] I have thought about well-defined extension points and marked everything else as `@internal` / `@private` diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 1657d96f2..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,199 +0,0 @@ -variables: - PLUGIN_NAME: SwagMigrationAssistant - PLATFORM_MIN_VERSION: 'v6.6.1.0' - PLATFORM_DEFAULT_VERSION: 'trunk' - PLATFORM_BRANCH: $PLATFORM_DEFAULT_VERSION - DEV_IMAGE: - value: ${CI_REGISTRY}/infrastructure/docker-base/ci-build:latest - NODE_VERSION: 20 - -include: - - project: 'shopware/6/product/platform' - ref: 'trunk' - file: '.gitlab/templates/plugin.yml' -# - component: gitlab.shopware.com/infrastructure/ci-component-library/kaniko-amd64@trunk -# inputs: -# job-suffix: "" -# stage: E2E -# destination-image: "${CI_REGISTRY_IMAGE}/ci-e2e" -# destination-tag: "${PLATFORM_BRANCH}-${CI_PIPELINE_ID}" -# dockerfile: .gitlab/Dockerfile -# enable-scan: "false" -# cache: "false" -# build-arg: "BASE_IMAGE=$PLATFORM_BASE_IMAGE" -# extra-args: "--build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN" - -Danger: - stage: test - image: - name: ghcr.io/shyim/danger-php:latest - entrypoint: [ "" ] - rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - before_script: [ ] - script: - - danger ci - -ecs: - stage: test - script: - - composer ecs - -Eslint (administration): - stage: test - variables: - APP_ENV: prod - ADMIN_PATH: $CI_PROJECT_DIR/src/Administration/Resources/app/administration - STOREFRONT_PATH: $CI_PROJECT_DIR/src/Storefront/Resources/app/storefront - script: - - '(cd $ADMIN_PATH && npm ci)' - - $CI_PROJECT_DIR/bin/console framework:schema -s 'entity-schema' $ADMIN_PATH/test/_mocks_/entity-schema.json - - npm --prefix $ADMIN_PATH run unit-setup - - composer admin:install - - composer admin:lint - -jest (administration): - rules: - # exists does not support variables, so we cannot use ${PLUGIN_SOURCE_DIR} here - - exists: - - tests/Jest/jest.config.js - variables: - APP_ENV: prod - ADMIN_PATH: $CI_PROJECT_DIR/src/Administration/Resources/app/administration - STOREFRONT_PATH: $CI_PROJECT_DIR/src/Storefront/Resources/app/storefront - script: - - '(cd $ADMIN_PATH && npm ci)' - - $CI_PROJECT_DIR/bin/console framework:schema -s 'entity-schema' $ADMIN_PATH/test/_mocks_/entity-schema.json - - npm --prefix $ADMIN_PATH run unit-setup - - composer admin:install - - composer admin:unit -- --ci - coverage: '/^\s?All files[^|]*\|[^|]*\s+([\d\.]+)/' - artifacts: - paths: - # allow inspection of the coverage report, otherwise it's not accessible - - custom/plugins/SwagMigrationAssistant/coverage/cobertura-coverage.xml - reports: - junit: - - custom/plugins/SwagMigrationAssistant/coverage/junit.xml - coverage_report: - coverage_format: cobertura - path: custom/plugins/SwagMigrationAssistant/coverage/cobertura-coverage.xml - -phpunit: - script: - - apt-get update && apt-get --assume-yes install default-mysql-client - - cd tests - - cd - - - mysql --skip_ssl -uroot -p"$MYSQL_ROOT_PASSWORD" --host mysql < tests/testData/sw55.sql - - php - -d pcov.enabled=1 -d pcov.directory=$PWD -d pcov.exclude='~(vendor|tests|node_modules)~' - ${PROJECT_ROOT}/vendor/bin/phpunit - --configuration phpunit.xml.dist - --log-junit ${CI_PROJECT_DIR}/phpunit.junit.xml - --colors=never - --coverage-cobertura ${CI_PROJECT_DIR}/cobertura.xml - --coverage-text #| grep -v -E '^Shopware\\|^ Methods:' # do not output covered files lines - parallel: - matrix: - - PLATFORM_BRANCH: [ $PLATFORM_MIN_VERSION, $PLATFORM_DEFAULT_VERSION ] - -phpstan: - script: - - composer dump-autoload --dev - - composer phpstan - -smoke-test: - stage: test - needs: [] - rules: - - !reference [.rules, skip] - - when: always - script: - - cd ../../.. - - php bin/console plugin:refresh - - php bin/console plugin:install --activate --clearCache ${PLUGIN_NAME} - - php bin/console plugin:uninstall ${PLUGIN_NAME} - -# disabled acceptance test, will be active as GH Action again after move to GitHub -#build image: -# variables: -# PLATFORM_BASE_IMAGE: ${CI_REGISTRY}/shopware/6/product/platform/ci-e2e:${PLATFORM_BRANCH} -# rules: -# # not supported, the downstream pipeline should find any issues anyway -# - if: "$CI_MERGE_REQUEST_LABELS =~ /.*branch::platform::match.*/" -# when: never -# - if: "$PARENT_PIPELINE_ID" -# when: never -# - when: always -# needs: [] -# parallel: -# matrix: -# - PLATFORM_BRANCH: [ $PLATFORM_MIN_VERSION, $PLATFORM_DEFAULT_VERSION ] -# -#build image downstream: -# extends: build image -# rules: -# - if: "$PARENT_PIPELINE_ID" -# needs: -# - pipeline: $PARENT_PIPELINE_ID -# job: build image -# artifacts: false -# -#acceptance: -# image: mcr.microsoft.com/playwright:v1.44.0-jammy -# stage: E2E -# needs: -# - job: build image -# optional: true -# - job: build image downstream -# optional: true -# rules: -# # not supported, the downstream pipeline should find any issues anyway -# - if: "$CI_MERGE_REQUEST_LABELS =~ /.*branch::platform::match.*/" -# when: never -# - when: always -# services: -# - name: mysql:8.3 -# alias: database -# entrypoint: -# [ -# "sh", -# "-c", -# "docker-entrypoint.sh mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password --sql-require-primary-key=ON", -# ] -# - name: redis:7.0 -# alias: redis -# - name: "${CI_REGISTRY_IMAGE}/ci-e2e:${PLATFORM_BRANCH}-${CI_PIPELINE_ID}" -# alias: shopware.test -# variables: -# DATABASE_URL: mysql://root:app@database:3306/root -# parallel: -# matrix: -# - PLATFORM_BRANCH: [ $PLATFORM_MIN_VERSION, $PLATFORM_DEFAULT_VERSION ] -# variables: -# # CI_DEBUG_SERVICES: "true" # This can be used to display the output of all service containers for debugging -# APP_ENV: prod -# SHOPWARE_HTTP_CACHE_ENABLED: 0 -# SHOPWARE_DISABLE_UPDATE_CHECK: "true" -# PROJECT_ROOT: /var/www/html -# MYSQL_ROOT_PASSWORD: app -# DATABASE_URL: mysql://root:app@database:3306/root -# APP_URL: http://shopware.test:8000 -# APP_DEBUG: 1 -# before_script: -# - cd tests/acceptance -# - npm ci -# script: -# - npx playwright test --workers=1 -# after_script: -# - | -# echo "Link to HTML report" -# echo "-------------------------------------------------------------------------------------------------------------------------------------------------------" -# echo "https://shopware.pages.apps.shopware.io/-/6/services/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/tests/acceptance/playwright-report/index.html" -# echo "-------------------------------------------------------------------------------------------------------------------------------------------------------" -# artifacts: -# expire_in: 1 day -# when: always -# paths: -# - $CI_PROJECT_DIR/tests/acceptance/test-results/* -# - $CI_PROJECT_DIR/tests/acceptance/playwright-report/* diff --git a/.gitlab/Dockerfile b/.gitlab/Dockerfile deleted file mode 100644 index 0aa0f6207..000000000 --- a/.gitlab/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -#syntax=docker/dockerfile:1.4 - -ARG BASE_IMAGE -FROM ${BASE_IMAGE} as base-image - -FROM ghcr.io/friendsofshopware/shopware-cli:latest-php-8.2 as build -ARG CI_JOB_TOKEN - -COPY --from=base-image /var/www/html /src -WORKDIR /src - -COPY . /src/custom/plugins/SwagMigrationAssistant - -ENV COMPOSER_ALLOW_SUPERUSER=1 - -RUN /src/vendor/bin/composer -d /src/custom/plugins/SwagMigrationAssistant require --no-update 'shopware/core:*' \ - && /src/vendor/bin/composer require --no-plugins --no-scripts -o swag/migration-assistant \ - && rm -Rf /src/var/cache/* \ - && /usr/local/bin/entrypoint.sh shopware-cli project admin-build /src \ - && /usr/local/bin/entrypoint.sh shopware-cli project storefront-build --skip-theme-compile /src \ - && find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + \ - && find . -name '.git' -type d -prune -exec rm -rf '{}' + \ - && rm -Rf /src/public/bundles || true - -FROM base-image - -COPY --from=build --chown=www-data --link /src /var/www/html - -USER root -RUN apk add --no-cache mysql-client - -USER www-data - -ADD .gitlab/post_install_migration_assistant.sh /usr/local/shopware/post_install.d/99_post_install_migration_assistant.sh diff --git a/.gitlab/install_test_data.php b/.gitlab/install_test_data.php deleted file mode 100644 index 25eec317c..000000000 --- a/.gitlab/install_test_data.php +++ /dev/null @@ -1,55 +0,0 @@ - Date: Mon, 24 Nov 2025 08:11:29 +0000 Subject: [PATCH 3/6] fix: support migrating email headers and footers (#87) --- .../Data/MailHeaderFooterProvider.php | 51 +++++++++++++++++++ src/DependencyInjection/dataProvider.xml | 5 ++ src/DependencyInjection/shopware6.xml | 21 ++++++++ .../DataSelection/DefaultEntities.php | 2 + .../Converter/MailHeaderFooterConverter.php | 45 ++++++++++++++++ .../Converter/SalesChannelConverter.php | 8 ++- .../BasicSettingsDataSelection.php | 2 + .../DataSet/MailHeaderFooterDataSet.php | 28 ++++++++++ .../Api/Reader/MailHeaderFooterReader.php | 20 ++++++++ .../Writer/MailHeaderFooterWriter.php | 21 ++++++++ .../src/module/swag-migration/snippet/de.json | 1 + .../src/module/swag-migration/snippet/en.json | 1 + .../MailHeaderFooterConverterTest.php | 43 ++++++++++++++++ .../MailHeaderFooter/01-HappyCase/input.php | 22 ++++++++ .../MailHeaderFooter/01-HappyCase/mapping.php | 16 ++++++ .../MailHeaderFooter/01-HappyCase/output.php | 22 ++++++++ 16 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 src/DataProvider/Provider/Data/MailHeaderFooterProvider.php create mode 100644 src/Profile/Shopware6/Converter/MailHeaderFooterConverter.php create mode 100644 src/Profile/Shopware6/DataSelection/DataSet/MailHeaderFooterDataSet.php create mode 100644 src/Profile/Shopware6/Gateway/Api/Reader/MailHeaderFooterReader.php create mode 100644 src/Profile/Shopware6/Writer/MailHeaderFooterWriter.php create mode 100644 tests/Profile/Shopware6/Converter/MailHeaderFooterConverterTest.php create mode 100644 tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/input.php create mode 100644 tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/mapping.php create mode 100644 tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/output.php diff --git a/src/DataProvider/Provider/Data/MailHeaderFooterProvider.php b/src/DataProvider/Provider/Data/MailHeaderFooterProvider.php new file mode 100644 index 000000000..9713327a7 --- /dev/null +++ b/src/DataProvider/Provider/Data/MailHeaderFooterProvider.php @@ -0,0 +1,51 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\DataProvider\Provider\Data; + +use Shopware\Core\Content\MailTemplate\Aggregate\MailHeaderFooter\MailHeaderFooterCollection; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterProvider extends AbstractProvider +{ + /** + * @param EntityRepository $mailHeaderFooterRepo + */ + public function __construct(private readonly EntityRepository $mailHeaderFooterRepo) + { + } + + public function getIdentifier(): string + { + return DefaultEntities::MAIL_HEADER_FOOTER; + } + + public function getProvidedData(int $limit, int $offset, Context $context): array + { + $criteria = new Criteria(); + $criteria->setLimit($limit); + $criteria->setOffset($offset); + $criteria->addAssociation('translations'); + $criteria->addSorting(new FieldSorting('id')); + $result = $this->mailHeaderFooterRepo->search($criteria, $context); + + return $this->cleanupSearchResult($result, [ + 'mailHeaderFooterId', + ]); + } + + public function getProvidedTotal(Context $context): int + { + return $this->readTotalFromRepo($this->mailHeaderFooterRepo, $context); + } +} diff --git a/src/DependencyInjection/dataProvider.xml b/src/DependencyInjection/dataProvider.xml index 2bb1e8393..664bee350 100644 --- a/src/DependencyInjection/dataProvider.xml +++ b/src/DependencyInjection/dataProvider.xml @@ -154,6 +154,11 @@
+ + + + + diff --git a/src/DependencyInjection/shopware6.xml b/src/DependencyInjection/shopware6.xml index 199ffaea8..a5f3e65ba 100644 --- a/src/DependencyInjection/shopware6.xml +++ b/src/DependencyInjection/shopware6.xml @@ -187,6 +187,11 @@ + + + + @@ -478,6 +483,10 @@ + + + + @@ -725,6 +734,11 @@ + + + + @@ -919,6 +933,13 @@ + + + + + + diff --git a/src/Migration/DataSelection/DefaultEntities.php b/src/Migration/DataSelection/DefaultEntities.php index 38e10a5ff..22fa556ec 100644 --- a/src/Migration/DataSelection/DefaultEntities.php +++ b/src/Migration/DataSelection/DefaultEntities.php @@ -70,6 +70,8 @@ final class DefaultEntities final public const LOCALE = 'locale'; + final public const MAIL_HEADER_FOOTER = 'mail_header_footer'; + final public const MAIL_TEMPLATE = 'mail_template'; final public const MAIL_TEMPLATE_TYPE = 'mail_template_type'; diff --git a/src/Profile/Shopware6/Converter/MailHeaderFooterConverter.php b/src/Profile/Shopware6/Converter/MailHeaderFooterConverter.php new file mode 100644 index 000000000..495286267 --- /dev/null +++ b/src/Profile/Shopware6/Converter/MailHeaderFooterConverter.php @@ -0,0 +1,45 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Profile\Shopware6\Converter; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Converter\ConvertStruct; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; +use SwagMigrationAssistant\Migration\MigrationContextInterface; +use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MailHeaderFooterDataSet; +use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterConverter extends ShopwareConverter +{ + public function supports(MigrationContextInterface $migrationContext): bool + { + return $migrationContext->getProfile()->getName() === Shopware6MajorProfile::PROFILE_NAME + && $this->getDataSetEntity($migrationContext) === MailHeaderFooterDataSet::getEntity(); + } + + protected function convertData(array $data): ConvertStruct + { + $converted = $data; + + $this->mainMapping = $this->getOrCreateMappingMainCompleteFacade( + DefaultEntities::MAIL_HEADER_FOOTER, + $data['id'], + $converted['id'] + ); + + $this->updateAssociationIds( + $converted['translations'], + DefaultEntities::LANGUAGE, + 'languageId', + DefaultEntities::MAIL_HEADER_FOOTER + ); + + return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); + } +} diff --git a/src/Profile/Shopware6/Converter/SalesChannelConverter.php b/src/Profile/Shopware6/Converter/SalesChannelConverter.php index 6bac57c34..d2884b192 100644 --- a/src/Profile/Shopware6/Converter/SalesChannelConverter.php +++ b/src/Profile/Shopware6/Converter/SalesChannelConverter.php @@ -109,6 +109,9 @@ protected function convertData(array $data): ConvertStruct if (isset($data['serviceCategoryId'])) { $converted['serviceCategoryId'] = $this->getMappingIdFacade(DefaultEntities::CATEGORY, $data['serviceCategoryId']); } + if (isset($data['mailHeaderFooterId'])) { + $converted['mailHeaderFooterId'] = $this->getMappingIdFacade(DefaultEntities::MAIL_HEADER_FOOTER, $data['mailHeaderFooterId']); + } $converted['languageId'] = $this->getMappingIdFacade(DefaultEntities::LANGUAGE, $data['languageId']); $converted['currencyId'] = $this->getMappingIdFacade(DefaultEntities::CURRENCY, $data['currencyId']); @@ -131,11 +134,6 @@ protected function convertData(array $data): ConvertStruct ); } - unset( - // ToDo implement if these associations are migrated - $converted['mailHeaderFooterId'] - ); - return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); } } diff --git a/src/Profile/Shopware6/DataSelection/BasicSettingsDataSelection.php b/src/Profile/Shopware6/DataSelection/BasicSettingsDataSelection.php index 243f7334a..668bf5851 100644 --- a/src/Profile/Shopware6/DataSelection/BasicSettingsDataSelection.php +++ b/src/Profile/Shopware6/DataSelection/BasicSettingsDataSelection.php @@ -21,6 +21,7 @@ use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\DeliveryTimeDataSet; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\DocumentBaseConfigDataSet; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\LanguageDataSet; +use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MailHeaderFooterDataSet; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MailTemplateDataSet; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MediaFolderDataSet; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MediaFolderInheritanceDataSet; @@ -79,6 +80,7 @@ public function getDataSets(): array new RuleDataSet(), new SnippetSetDataSet(), new SnippetDataSet(), + new MailHeaderFooterDataSet(), new MailTemplateDataSet(), new DeliveryTimeDataSet(), new ShippingMethodDataSet(), diff --git a/src/Profile/Shopware6/DataSelection/DataSet/MailHeaderFooterDataSet.php b/src/Profile/Shopware6/DataSelection/DataSet/MailHeaderFooterDataSet.php new file mode 100644 index 000000000..045b52460 --- /dev/null +++ b/src/Profile/Shopware6/DataSelection/DataSet/MailHeaderFooterDataSet.php @@ -0,0 +1,28 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSet; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; +use SwagMigrationAssistant\Migration\MigrationContextInterface; +use SwagMigrationAssistant\Profile\Shopware6\Shopware6ProfileInterface; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterDataSet extends DataSet +{ + public static function getEntity(): string + { + return DefaultEntities::MAIL_HEADER_FOOTER; + } + + public function supports(MigrationContextInterface $migrationContext): bool + { + return $migrationContext->getProfile() instanceof Shopware6ProfileInterface; + } +} diff --git a/src/Profile/Shopware6/Gateway/Api/Reader/MailHeaderFooterReader.php b/src/Profile/Shopware6/Gateway/Api/Reader/MailHeaderFooterReader.php new file mode 100644 index 000000000..157675ecc --- /dev/null +++ b/src/Profile/Shopware6/Gateway/Api/Reader/MailHeaderFooterReader.php @@ -0,0 +1,20 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Profile\Shopware6\Gateway\Api\Reader; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterReader extends ApiReader +{ + protected function getIdentifier(): string + { + return DefaultEntities::MAIL_HEADER_FOOTER; + } +} diff --git a/src/Profile/Shopware6/Writer/MailHeaderFooterWriter.php b/src/Profile/Shopware6/Writer/MailHeaderFooterWriter.php new file mode 100644 index 000000000..2e9e461ba --- /dev/null +++ b/src/Profile/Shopware6/Writer/MailHeaderFooterWriter.php @@ -0,0 +1,21 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Profile\Shopware6\Writer; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; +use SwagMigrationAssistant\Migration\Writer\AbstractWriter; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterWriter extends AbstractWriter +{ + public function supports(): string + { + return DefaultEntities::MAIL_HEADER_FOOTER; + } +} diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json index 7361dfc3c..c70c97e47 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/de.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/de.json @@ -324,6 +324,7 @@ "custom_field_set": "Zusatzfelder", "snippet": "Textbausteine", "snippet_set": "Textbaustein-Sets", + "mail_header_footer": "E-Mail-Kopf- und Fußzeilen", "mail_template": "E-Mail-Template", "product_feature_set": "Wesentliche Merkmale", "product_sorting": "Produkt-Sortierungen", diff --git a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json index 82a3e2490..9706c7ddb 100644 --- a/src/Resources/app/administration/src/module/swag-migration/snippet/en.json +++ b/src/Resources/app/administration/src/module/swag-migration/snippet/en.json @@ -324,6 +324,7 @@ "custom_field_set": "Custom field sets", "snippet": "Snippets", "snippet_set": "Snippet sets", + "mail_header_footer": "Email headers and footers", "mail_template": "Email templates", "product_feature_set": "Essential characteristics", "product_sorting": "Product sorting", diff --git a/tests/Profile/Shopware6/Converter/MailHeaderFooterConverterTest.php b/tests/Profile/Shopware6/Converter/MailHeaderFooterConverterTest.php new file mode 100644 index 000000000..f872ef45c --- /dev/null +++ b/tests/Profile/Shopware6/Converter/MailHeaderFooterConverterTest.php @@ -0,0 +1,43 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Test\Profile\Shopware6\Converter; + +use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Migration\Converter\ConverterInterface; +use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSet; +use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; +use SwagMigrationAssistant\Migration\Mapping\MappingServiceInterface; +use SwagMigrationAssistant\Migration\Media\MediaFileServiceInterface; +use SwagMigrationAssistant\Profile\Shopware6\Converter\MailHeaderFooterConverter; +use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\MailHeaderFooterDataSet; + +#[Package('fundamentals@after-sales')] +class MailHeaderFooterConverterTest extends ShopwareConverterTest +{ + protected function createConverter( + MappingServiceInterface $mappingService, + LoggingServiceInterface $loggingService, + MediaFileServiceInterface $mediaFileService, + ?array $mappingArray = [], + ): ConverterInterface { + return new MailHeaderFooterConverter( + $mappingService, + $loggingService, + ); + } + + protected function createDataSet(): DataSet + { + return new MailHeaderFooterDataSet(); + } + + protected static function getFixtureBasePath(): string + { + return __DIR__ . '/../../../_fixtures/Shopware6/MailHeaderFooter/'; + } +} diff --git a/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/input.php b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/input.php new file mode 100644 index 000000000..575af794f --- /dev/null +++ b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/input.php @@ -0,0 +1,22 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'id' => 'a1b2c3d4e5f64a7b8c9d0e1f2a3b4c5d', + 'systemDefault' => false, + 'translations' => [ + [ + 'languageId' => '2fbb5fe2e29a4d70aa5854ce7ce3e20b', + 'name' => 'Test Header Footer', + 'description' => 'Test description', + 'headerHtml' => '
Test HTML Header
', + 'headerPlain' => 'Test Plain Header', + 'footerHtml' => '
Test HTML Footer
', + 'footerPlain' => 'Test Plain Footer', + ], + ], +]; diff --git a/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/mapping.php b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/mapping.php new file mode 100644 index 000000000..668e2d734 --- /dev/null +++ b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/mapping.php @@ -0,0 +1,16 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; + +return [ + [ + 'entityName' => DefaultEntities::LANGUAGE, + 'oldIdentifier' => '2fbb5fe2e29a4d70aa5854ce7ce3e20b', + 'newIdentifier' => '5dd637353d044752ae6a8c6e7f53430b', + ], +]; diff --git a/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/output.php b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/output.php new file mode 100644 index 000000000..38459e647 --- /dev/null +++ b/tests/_fixtures/Shopware6/MailHeaderFooter/01-HappyCase/output.php @@ -0,0 +1,22 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'id' => 'a1b2c3d4e5f64a7b8c9d0e1f2a3b4c5d', + 'systemDefault' => false, + 'translations' => [ + [ + 'languageId' => '5dd637353d044752ae6a8c6e7f53430b', + 'name' => 'Test Header Footer', + 'description' => 'Test description', + 'headerHtml' => '
Test HTML Header
', + 'headerPlain' => 'Test Plain Header', + 'footerHtml' => '
Test HTML Footer
', + 'footerPlain' => 'Test Plain Footer', + ], + ], +]; From 1cf56ed022c5068b267772062e79f4f23f6ccbde Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 24 Nov 2025 12:42:01 +0200 Subject: [PATCH 4/6] fix: dont migrate core.app.shopIdV2 (#91) --- .../Provider/Data/SystemConfigProvider.php | 1 + .../Controller/DataProviderControllerTest.php | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/Controller/DataProviderControllerTest.php diff --git a/src/DataProvider/Provider/Data/SystemConfigProvider.php b/src/DataProvider/Provider/Data/SystemConfigProvider.php index a5f9ef5f6..29efed507 100644 --- a/src/DataProvider/Provider/Data/SystemConfigProvider.php +++ b/src/DataProvider/Provider/Data/SystemConfigProvider.php @@ -26,6 +26,7 @@ class SystemConfigProvider extends AbstractProvider public static array $CONFIG_KEY_BLOCK_LIST = [ // shop instance, license related or consent data 'core.app.shopId', + 'core.app.shopIdV2', 'core.basicInformation.shopName', 'core.basicInformation.activeCaptchas', 'core.basicInformation.activeCaptchasV2', diff --git a/tests/Controller/DataProviderControllerTest.php b/tests/Controller/DataProviderControllerTest.php new file mode 100644 index 000000000..6af110489 --- /dev/null +++ b/tests/Controller/DataProviderControllerTest.php @@ -0,0 +1,73 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagMigrationAssistant\Test\Controller; + +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Test\TestCaseBase\AdminApiTestBehaviour; +use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour; +use Shopware\Core\Framework\Test\TestCaseBase\SalesChannelApiTestBehaviour; +use Shopware\Core\Framework\Uuid\Uuid; +use SwagMigrationAssistant\DataProvider\Provider\Data\SystemConfigProvider; +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; + +#[Package('fundamentals@after-sales')] +class DataProviderControllerTest extends TestCase +{ + use AdminApiTestBehaviour; + use IntegrationTestBehaviour; + use SalesChannelApiTestBehaviour; + + public function testSystemConfigEntriesGetFiltered(): void + { + $salesChannelId = Uuid::randomHex(); + + $this->createSalesChannel(['id' => $salesChannelId]); + + $configEntries = []; + foreach (SystemConfigProvider::$CONFIG_KEY_BLOCK_LIST as $configKey) { + $configEntries[] = [ + 'configurationKey' => $configKey, + 'configurationValue' => 'should be filtered', + 'salesChannelId' => $salesChannelId, + ]; + } + + $configEntries[] = [ + 'configurationKey' => 'core.some.allowedConfigKey', + 'configurationValue' => 'should be visible', + 'salesChannelId' => $salesChannelId, + ]; + + $this->getContainer()->get('system_config.repository')->create($configEntries, Context::createDefaultContext()); + + $browser = $this->createClient(); + + $browser->request( + 'GET', + '/api/_action/data-provider/get-data', + [ + 'identifier' => DefaultEntities::SYSTEM_CONFIG, + ] + ); + + $response = $browser->getResponse()->getContent(); + + static::assertNotFalse($response); + + $response = json_decode($response, true); + + foreach ($response as $configEntry) { + static::assertNotContains($configEntry['configurationKey'], SystemConfigProvider::$CONFIG_KEY_BLOCK_LIST); + } + + static::assertNotEmpty(array_filter($response, static fn ($entry) => $entry['configurationKey'] === 'core.some.allowedConfigKey')); + } +} From 699340ca6d7757f112e5112e33cba2e14d0a6d21 Mon Sep 17 00:00:00 2001 From: Dennis Garding <11271248+DennisGarding@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:35:33 +0100 Subject: [PATCH 5/6] fix: migrated document availability in frontend (#92) --- src/DependencyInjection/shopware.xml | 1 + .../Lookup/GlobalDocumentBaseConfigLookup.php | 25 ++++++ .../Processor/MediaProcessingProcessor.php | 1 + src/Migration/MigrationContextInterface.php | 2 + .../Converter/OrderDocumentConverter.php | 35 +++++++-- .../GlobalDocumentBaseConfigLookupTest.php | 78 +++++++++++++++++++ .../Converter/OrderDocumentConverterTest.php | 65 +++++++++++++++- .../input.php | 76 ++++++++++++++++++ .../mapping.php | 16 ++++ .../media.php | 16 ++++ .../output.php | 70 +++++++++++++++++ 11 files changed, 376 insertions(+), 9 deletions(-) create mode 100644 tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/input.php create mode 100644 tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/mapping.php create mode 100644 tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/media.php create mode 100644 tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/output.php diff --git a/src/DependencyInjection/shopware.xml b/src/DependencyInjection/shopware.xml index b893496aa..e0a24ff23 100644 --- a/src/DependencyInjection/shopware.xml +++ b/src/DependencyInjection/shopware.xml @@ -178,6 +178,7 @@ +
|null> + */ + private array $configCache = []; + /** * @param EntityRepository $documentBaseConfigRepository * @@ -51,8 +56,28 @@ public function get(string $documentTypeId, Context $context): ?string return $baseConfigId; } + /** + * @return array|null + */ + public function getBaseConfig(string $baseConfigId, Context $context): ?array + { + if (\array_key_exists($baseConfigId, $this->configCache)) { + return $this->configCache[$baseConfigId]; + } + + $criteria = new Criteria([$baseConfigId]); + + $baseConfig = $this->documentBaseConfigRepository->search($criteria, $context)->first(); + + $config = $baseConfig?->getConfig(); + $this->configCache[$baseConfigId] = $config; + + return $config; + } + public function reset(): void { $this->cache = []; + $this->configCache = []; } } diff --git a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php index 38968fcc2..ca440512c 100644 --- a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php +++ b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php @@ -98,6 +98,7 @@ public function process( if ($currentDataSet === null) { try { $currentDataSet = $this->dataSetRegistry->getDataSet($migrationContext, $mediaFile['entity']); + $migrationContext->setDataSet($currentDataSet); } catch (DataSetNotFoundException $e) { $this->logDataSetNotFoundException($migrationContext, $mediaFile); diff --git a/src/Migration/MigrationContextInterface.php b/src/Migration/MigrationContextInterface.php index 60da42ce4..980c8ddb1 100644 --- a/src/Migration/MigrationContextInterface.php +++ b/src/Migration/MigrationContextInterface.php @@ -31,4 +31,6 @@ public function getLimit(): int; public function getGateway(): GatewayInterface; public function setGateway(GatewayInterface $gateway): void; + + public function setDataSet(DataSet $dataSet): void; } diff --git a/src/Profile/Shopware/Converter/OrderDocumentConverter.php b/src/Profile/Shopware/Converter/OrderDocumentConverter.php index 0d098333d..6029f02a6 100644 --- a/src/Profile/Shopware/Converter/OrderDocumentConverter.php +++ b/src/Profile/Shopware/Converter/OrderDocumentConverter.php @@ -20,6 +20,7 @@ use SwagMigrationAssistant\Migration\Logging\Log\EmptyNecessaryFieldRunLog; use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; use SwagMigrationAssistant\Migration\Mapping\Lookup\DocumentTypeLookup; +use SwagMigrationAssistant\Migration\Mapping\Lookup\GlobalDocumentBaseConfigLookup; use SwagMigrationAssistant\Migration\Mapping\Lookup\MediaDefaultFolderLookup; use SwagMigrationAssistant\Migration\Mapping\MappingServiceInterface; use SwagMigrationAssistant\Migration\Media\MediaFileServiceInterface; @@ -45,6 +46,7 @@ public function __construct( protected MediaFileServiceInterface $mediaFileService, protected readonly MediaDefaultFolderLookup $mediaFolderLookup, protected readonly DocumentTypeLookup $documentTypeLookup, + protected readonly GlobalDocumentBaseConfigLookup $globalDocumentBaseConfigLookup, ) { parent::__construct($mappingService, $loggingService); } @@ -143,7 +145,16 @@ public function convert(array $data, Context $context, MigrationContextInterface $converted['fileType'] = FileTypes::PDF; $converted['static'] = true; $converted['deepLinkCode'] = Random::getAlphanumericString(32); - $converted['config'] = []; + if (\array_key_exists('sent', $data)) { + $converted['sent'] = $data['sent']; + } else { + // In Shopware 5 "sent" not exists, so we force it to true, because we assume that if there is a document, the customer received it. + $converted['sent'] = true; + } + + $documentType = $this->getDocumentType($data['documenttype']); + $converted['documentType'] = $documentType; + $converted['config'] = $this->getBaseDocumentTypeConfig($documentType['id'], $context); if (isset($data['docID'])) { $converted['config']['documentNumber'] = $data['docID']; @@ -153,10 +164,6 @@ public function convert(array $data, Context $context, MigrationContextInterface unset($data['docID']); } - - $documentType = $this->getDocumentType($data['documenttype']); - - $converted['documentType'] = $documentType; unset($data['documenttype']); if (isset($data['attributes'])) { @@ -223,6 +230,24 @@ protected function getDocumentType(array $data): array return $documentType; } + /** + * @return array + */ + protected function getBaseDocumentTypeConfig(string $documentTypeId, Context $context): array + { + $documentConfigId = $this->globalDocumentBaseConfigLookup->get($documentTypeId, $context); + if ($documentConfigId === null) { + return []; + } + + $documentConfig = $this->globalDocumentBaseConfigLookup->getBaseConfig($documentConfigId, $context); + if ($documentConfig === null) { + return []; + } + + return $documentConfig; + } + /** * @param array $data * diff --git a/tests/Migration/Mapping/Lookup/GlobalDocumentBaseConfigLookupTest.php b/tests/Migration/Mapping/Lookup/GlobalDocumentBaseConfigLookupTest.php index 917b1a6bf..fe56babc0 100644 --- a/tests/Migration/Mapping/Lookup/GlobalDocumentBaseConfigLookupTest.php +++ b/tests/Migration/Mapping/Lookup/GlobalDocumentBaseConfigLookupTest.php @@ -45,11 +45,47 @@ public function testReset(): void $cacheProperty = new \ReflectionProperty(GlobalDocumentBaseConfigLookup::class, 'cache'); $cacheProperty->setAccessible(true); + $configCacheProperty = new \ReflectionProperty(GlobalDocumentBaseConfigLookup::class, 'configCache'); + $configCacheProperty->setAccessible(true); + static::assertNotEmpty($cacheProperty->getValue($globalDocumentBaseConfigLookup)); + static::assertNotEmpty($configCacheProperty->getValue($globalDocumentBaseConfigLookup)); $globalDocumentBaseConfigLookup->reset(); static::assertEmpty($cacheProperty->getValue($globalDocumentBaseConfigLookup)); + static::assertEmpty($configCacheProperty->getValue($globalDocumentBaseConfigLookup)); + } + + public function testGetBaseConfig(): void + { + $context = Context::createDefaultContext(); + + $data = self::getDatabaseData(); + $documentTypeID = $data[0]['documentTypeId']; + static::assertIsString($documentTypeID); + + $globalDocumentBaseConfigLookup = $this->getGlobalDocumentBaseConfigLookup(); + $configId = $globalDocumentBaseConfigLookup->get($documentTypeID, $context); + static::assertIsString($configId); + + $baseConfig = $globalDocumentBaseConfigLookup->getBaseConfig($configId, $context); + + static::assertIsArray($baseConfig); + static::assertArrayHasKey('fileTypes', $baseConfig); + static::assertArrayHasKey('referencedDocumentType', $baseConfig); + static::assertSame('invoice', $baseConfig['referencedDocumentType']); + } + + public function testGetBaseConfigFromCache(): void + { + $context = Context::createDefaultContext(); + + $globalDocumentBaseConfigLookup = $this->getMockedGlobalDocumentBaseConfigLookup(); + $baseConfigResult = $globalDocumentBaseConfigLookup->getBaseConfig('anyConfigId', $context); + + static::assertIsArray($baseConfigResult); + static::assertSame($this->getCachedConfig(), $baseConfigResult); } /** @@ -107,6 +143,48 @@ private function getMockedGlobalDocumentBaseConfigLookup(): GlobalDocumentBaseCo $reflectionProperty->setValue($globalDocumentBaseConfigLookup, $cache); + $configCache = ['anyConfigId' => $this->getCachedConfig()]; + $reflectionProperty = new \ReflectionProperty(GlobalDocumentBaseConfigLookup::class, 'configCache'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($globalDocumentBaseConfigLookup, $configCache); + return $globalDocumentBaseConfigLookup; } + + /** + * @return array + */ + private function getCachedConfig(): array + { + return [ + 'vatId' => 'anyVatId', + 'bankBic' => 'anyBankBic', + 'bankIban' => 'anyBankIban', + 'bankName' => 'anyBankName', + 'pageSize' => 'a4', + 'fileTypes' => [ + 0 => 'html', + 1 => 'pdf', + ], + 'taxNumber' => 'anyTaxNumber', + 'taxOffice' => 'anyTaxOffice', + 'companyName' => 'Example Company', + 'itemsPerPage' => 10, + 'displayFooter' => true, + 'displayHeader' => true, + 'displayPrices' => true, + 'companyAddress' => 'anyAddress', + 'pageOrientation' => 'portrait', + 'displayLineItems' => true, + 'displayPageCount' => true, + 'executiveDirector' => 'anyExecutiveDirector', + 'placeOfFulfillment' => 'anyPlaceOfFulfillment', + 'placeOfJurisdiction' => 'anyPlaceOfJurisdiction', + 'displayReturnAddress' => true, + 'displayCompanyAddress' => true, + 'diplayLineItemPosition' => true, + 'referencedDocumentType' => 'anyReferencedDocumentType', + 'displayAdditionalNoteDelivery' => false, + ]; + } } diff --git a/tests/Profile/Shopware54/Converter/OrderDocumentConverterTest.php b/tests/Profile/Shopware54/Converter/OrderDocumentConverterTest.php index b9fe71033..eced7e391 100644 --- a/tests/Profile/Shopware54/Converter/OrderDocumentConverterTest.php +++ b/tests/Profile/Shopware54/Converter/OrderDocumentConverterTest.php @@ -18,6 +18,7 @@ use SwagMigrationAssistant\Migration\Logging\Log\DocumentTypeNotSupported; use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; use SwagMigrationAssistant\Migration\Mapping\Lookup\DocumentTypeLookup; +use SwagMigrationAssistant\Migration\Mapping\Lookup\GlobalDocumentBaseConfigLookup; use SwagMigrationAssistant\Migration\Mapping\Lookup\MediaDefaultFolderLookup; use SwagMigrationAssistant\Migration\Mapping\MappingServiceInterface; use SwagMigrationAssistant\Migration\MigrationContext; @@ -63,7 +64,8 @@ protected function setUp(): void $this->loggingService, $mediaFileService, $this->createMock(MediaDefaultFolderLookup::class), - $this->createMock(DocumentTypeLookup::class) + $this->createMock(DocumentTypeLookup::class), + $this->createDocumentBaseConfigLookupMock(), ); $connectionId = Uuid::randomHex(); $this->runId = Uuid::randomHex(); @@ -145,10 +147,15 @@ public function testConvert(): void static::assertArrayHasKey('documentType', $converted); static::assertSame('pdf', $converted['fileType']); static::assertTrue($converted['static']); + static::assertTrue($converted['sent']); static::assertSame('Rechnung', $converted['documentType']['name']); static::assertSame('invoice', $converted['documentType']['technicalName']); - static::assertSame($orderDocumentData[0]['docID'], $converted['config']['documentNumber']); - static::assertSame($orderDocumentData[0]['docID'], $converted['config']['custom']['invoiceNumber']); + + $expectedConfig = $this->getDefaultConfig(); + $expectedConfig['documentNumber'] = $orderDocumentData[0]['docID']; + $expectedConfig['custom']['invoiceNumber'] = $orderDocumentData[0]['docID']; + + static::assertSame($expectedConfig, $converted['config']); } public function testConvertShouldLogUnknownType(): void @@ -270,15 +277,65 @@ private function createDocumentConverter(string $converterClass, MappingServiceI $loggingService = new DummyLoggingService(); } + $documentBaseConfigLookupMock = $this->createMock(GlobalDocumentBaseConfigLookup::class); + $documentBaseConfigLookupMock->method('getBaseConfig')->willReturn([]); + $instance = new $converterClass( $mappingService, $loggingService, new DummyMediaFileService(), $this->createMock(MediaDefaultFolderLookup::class), - $this->createMock(DocumentTypeLookup::class) + $this->createMock(DocumentTypeLookup::class), + $documentBaseConfigLookupMock, ); static::assertInstanceOf(ShopwareConverter::class, $instance); return $instance; } + + private function createDocumentBaseConfigLookupMock(): GlobalDocumentBaseConfigLookup + { + $documentBaseConfigLookupMock = $this->createMock(GlobalDocumentBaseConfigLookup::class); + $documentBaseConfigLookupMock->method('get')->willReturn(Uuid::randomHex()); + $documentBaseConfigLookupMock->method('getBaseConfig')->willReturn($this->getDefaultConfig()); + + return $documentBaseConfigLookupMock; + } + + /** + * @return array + */ + private function getDefaultConfig(): array + { + return [ + 'vatId' => '', + 'bankBic' => '', + 'bankIban' => '', + 'bankName' => '', + 'pageSize' => 'a4', + 'fileTypes' => [ + 'html', + 'pdf', + ], + 'taxNumber' => '', + 'taxOffice' => '', + 'companyName' => 'Example Company', + 'itemsPerPage' => 10, + 'displayFooter' => true, + 'displayHeader' => true, + 'displayPrices' => true, + 'companyAddress' => '', + 'pageOrientation' => 'portrait', + 'displayLineItems' => true, + 'displayPageCount' => true, + 'executiveDirector' => '', + 'placeOfFulfillment' => '', + 'placeOfJurisdiction' => '', + 'displayReturnAddress' => true, + 'displayCompanyAddress' => true, + 'displayLineItemPosition' => true, + 'referencedDocumentType' => 'invoice', + 'displayAdditionalNoteDelivery' => false, + ]; + } } diff --git a/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/input.php b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/input.php new file mode 100644 index 000000000..8b82e1a62 --- /dev/null +++ b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/input.php @@ -0,0 +1,76 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'orderId' => '32865ab671214a9fbaa215d1b22af44d', + 'fileType' => 'pdf', + 'config' => [ + 'displayPrices' => false, + 'logo' => null, + 'filenamePrefix' => 'delivery_note_', + 'filenameSuffix' => '', + 'documentNumber' => '1000', + 'pageOrientation' => 'portrait', + 'pageSize' => 'a4', + 'displayFooter' => true, + 'displayHeader' => true, + 'displayLineItems' => true, + 'displayLineItemPosition' => null, + 'itemsPerPage' => 10, + 'displayPageCount' => true, + 'displayCompanyAddress' => true, + 'title' => null, + 'companyAddress' => 'Muster AG - Ebbinghoff 10 - 48624 Schöppingen', + 'companyName' => 'Muster AG', + 'companyEmail' => null, + 'companyUrl' => null, + 'taxNumber' => '000111000', + 'taxOffice' => 'Coesfeld', + 'vatId' => 'XX 111 222 333', + 'bankName' => 'Kreissparkasse Münster', + 'bankIban' => 'DE11111222223333344444', + 'bankBic' => 'SWSKKEFF', + 'placeOfJurisdiction' => 'Coesfeld', + 'placeOfFulfillment' => 'Coesfeld', + 'executiveDirector' => 'Max Mustermann', + 'custom' => [ + 'deliveryDate' => '2020-12-03T00:00:00+00:00', + 'deliveryNoteDate' => '2020-12-02T11:13:58.636Z', + 'deliveryNoteNumber' => '1000', + ], + 'extensions' => [ + ], + 'name' => 'delivery_note', + 'global' => true, + 'documentTypeId' => '3292943c32e5499f9a54cc5ff1a16abe', + 'translated' => [ + ], + 'id' => 'be54fb58fe374cd3865a82796350e6d5', + 'diplayLineItemPosition' => true, + 'documentComment' => 'Comment here', + 'documentDate' => '2020-12-02T11:13:58.810Z', + ], + 'sent' => true, + 'static' => false, + 'deepLinkCode' => 'iFlpLFZam1KS8AAT4SV9Tn7nEsSnSQIY', + 'documentType' => [ + 'name' => 'Delivery note', + 'technicalName' => 'delivery_note', + 'id' => '3292943c32e5499f9a54cc5ff1a16abe', + ], + 'documentMediaFile' => [ + 'fileExtension' => 'pdf', + 'fileSize' => 211211, + 'uploadedAt' => '2021-01-06T07:01:48.896+00:00', + 'url' => 'http://nextsupport.local/api/v3/_action/document/2efbab03c3d84f968bcd5636f9aa4057/NMXl9wyGzE2aB2CAarYHYg2xqtx8XXLN', + 'fileName' => 'delivery_note_10001', + 'mediaFolderId' => 'e49eafe11fbe4396bf1102caeec1f67f', + 'private' => true, + 'id' => '7bcdcfa0af944bb6ab05d2f29811cbb6', + ], + 'id' => '439564d3fc194166bf1c2c9e21942469', +]; diff --git a/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/mapping.php b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/mapping.php new file mode 100644 index 000000000..735a57e69 --- /dev/null +++ b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/mapping.php @@ -0,0 +1,16 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; + +return [ + [ + 'entityName' => DefaultEntities::ORDER_DOCUMENT_TYPE, + 'oldIdentifier' => 'delivery_note', + 'newIdentifier' => '41248655c60c4dd58cde43f14cd4f149', + ], +]; diff --git a/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/media.php b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/media.php new file mode 100644 index 000000000..86b97d3b1 --- /dev/null +++ b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/media.php @@ -0,0 +1,16 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + [ + 'entity' => 'order_document', + 'uri' => 'http://nextsupport.local/api/v3/_action/document/2efbab03c3d84f968bcd5636f9aa4057/NMXl9wyGzE2aB2CAarYHYg2xqtx8XXLN', + 'fileName' => 'delivery_note_10001', + 'fileSize' => 211211, + 'mediaId' => '7bcdcfa0af944bb6ab05d2f29811cbb6', + ], +]; diff --git a/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/output.php b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/output.php new file mode 100644 index 000000000..f918e6f84 --- /dev/null +++ b/tests/_fixtures/Shopware6/Document/03-WithGeneratedDocumentAndSentIsTrue/output.php @@ -0,0 +1,70 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'orderId' => '32865ab671214a9fbaa215d1b22af44d', + 'fileType' => 'pdf', + 'config' => [ + 'displayPrices' => false, + 'logo' => null, + 'filenamePrefix' => 'delivery_note_', + 'filenameSuffix' => '', + 'documentNumber' => '1000', + 'pageOrientation' => 'portrait', + 'pageSize' => 'a4', + 'displayFooter' => true, + 'displayHeader' => true, + 'displayLineItems' => true, + 'displayLineItemPosition' => null, + 'itemsPerPage' => 10, + 'displayPageCount' => true, + 'displayCompanyAddress' => true, + 'title' => null, + 'companyAddress' => 'Muster AG - Ebbinghoff 10 - 48624 Schöppingen', + 'companyName' => 'Muster AG', + 'companyEmail' => null, + 'companyUrl' => null, + 'taxNumber' => '000111000', + 'taxOffice' => 'Coesfeld', + 'vatId' => 'XX 111 222 333', + 'bankName' => 'Kreissparkasse Münster', + 'bankIban' => 'DE11111222223333344444', + 'bankBic' => 'SWSKKEFF', + 'placeOfJurisdiction' => 'Coesfeld', + 'placeOfFulfillment' => 'Coesfeld', + 'executiveDirector' => 'Max Mustermann', + 'custom' => [ + 'deliveryDate' => '2020-12-03T00:00:00+00:00', + 'deliveryNoteDate' => '2020-12-02T11:13:58.636Z', + 'deliveryNoteNumber' => '1000', + ], + 'extensions' => [ + ], + 'name' => 'delivery_note', + 'global' => true, + 'documentTypeId' => '41248655c60c4dd58cde43f14cd4f149', + 'translated' => [ + ], + 'id' => 'be54fb58fe374cd3865a82796350e6d5', + 'diplayLineItemPosition' => true, + 'documentComment' => 'Comment here', + 'documentDate' => '2020-12-02T11:13:58.810Z', + ], + 'sent' => true, + 'static' => false, + 'deepLinkCode' => 'iFlpLFZam1KS8AAT4SV9Tn7nEsSnSQIY', + 'documentMediaFile' => [ + 'uploadedAt' => '2021-01-06T07:01:48.896+00:00', + 'fileName' => 'delivery_note_10001', + 'mediaFolderId' => 'e49eafe11fbe4396bf1102caeec1f67f', + 'private' => true, + 'id' => '7bcdcfa0af944bb6ab05d2f29811cbb6', + 'hasFile' => false, + ], + 'id' => '439564d3fc194166bf1c2c9e21942469', + 'documentTypeId' => '41248655c60c4dd58cde43f14cd4f149', +]; From 54b31ad598f2e66e7923d6c98aba97b7bbace870 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Wed, 3 Dec 2025 15:28:13 +0200 Subject: [PATCH 6/6] chore: prepare release 15.0.4 (#96) --- CHANGELOG.md | 8 ++++++-- CHANGELOG_de-DE.md | 8 ++++++-- UPGRADE.md | 3 +++ composer.json | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa953bd64..e777ea20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 15.0.4 +- Added `shopIdV2` to the list of ignored system config entries for Shopware 6 connections +- Media processing has been simplified so that the UI can show the actual progress + # 15.0.3 - Fixed translations of error groups missing details like the entity - Fixed handling of customers without default payment method in SW6.7 migrations @@ -48,7 +52,7 @@ - MIG-1016 - Improves the warnings for different default currency and default language in the data selection. - MIG-1016 - Added new block `{% block swag_migration_confirm_warning_alert %}` in `swag-migration/component/card/swag-migration-confirm-warning/swag-migration-confirm-warning.html.twig`. - MIG-1037 - Fixes a rare issue that in certain situations not all entities are migrated (some were skipped). Was detected during translations of SW5. - + # 13.0.0 - MIG-945 - [BREAKING] Changed method name `getMedia` to `setMedia` in `SwagMigrationAssistant\Profile\Shopware\Converter\PropertyGroupOptionConverter` - MIG-945 - [BREAKING] Removed cli command `migration:migrate` use `migration:start` instead @@ -149,7 +153,7 @@ - MIG-943 - [BREAKING] Rename method in class `\SwagMigrationAssistant\Profile\Shopware\Converter\OrderConverter` from `getCountryTranslation` to `applyCountryTranslation` - MIG-943 - [BREAKING] Rename method in class `\SwagMigrationAssistant\Profile\Shopware\Converter\OrderConverter` from `getCountryStateTranslation` to `applyCountryStateTranslation` - MIG-943 - [BREAKING] Changed thrown exception from `AssociationEntityRequiredMissingException` to `SwagMigrationAssistant\Exception\MigrationException::associationMissing` in method `\SwagMigrationAssistant\Profile\Shopware\Converter\OrderConverter::convert` -- MIG-967, MIG-866 - Improving the migration of order documents +- MIG-967, MIG-866 - Improving the migration of order documents # 10.0.1 - MIG-971 - Fix compatibility with Shopware 6.6.0.x diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md index 639bea015..88bcaada7 100644 --- a/CHANGELOG_de-DE.md +++ b/CHANGELOG_de-DE.md @@ -1,3 +1,7 @@ +# 15.0.4 +- `shopIdV2` wurde zur Liste der ignorierten Systemkonfigurationseinträge für Shopware 6-Verbindungen hinzugefügt +- Die Medienverarbeitung wurde vereinfacht, sodass die Benutzeroberfläche den tatsächlichen Fortschritt anzeigen kann + # 15.0.3 - Übersetzungen von Fehlergruppen behoben, welche Details wie den Entitätsnamen nicht darstellten - Fehler bei Kunden ohne Standardzahlungsmethode in SW6.7 Migrationen behoben @@ -343,7 +347,7 @@ - MIG-114 - Migration der Hauptvarianten-Information ermöglichen - MIG-118 - Korrigiert die Migration der Kredit-Bestellpositionen - MIG-120 - Behebt ein Problem beim Laden des Premappings -- MIG-162 - Behebt ein Problem bei der Migration von Produkten mit leeren Freitextfeldern +- MIG-162 - Behebt ein Problem bei der Migration von Produkten mit leeren Freitextfeldern - MIG-167 - Behebt ein Problem bei der Migration von Freitextfeldwerten - MIG-168 - Optimiertes Request Handling @@ -454,7 +458,7 @@ # 1.0.0 - PT-11113 - Anpassung der Plugin icons - PT-11111 - Anpassung des Profilicons für externe Profile -- NTR - Behebt ein Problem nach dem Installieren von extern Profilen +- NTR - Behebt ein Problem nach dem Installieren von extern Profilen - NTR - Snippet renaming - PT-11252 - Nummernkreise werden jetzt in den Basisdaten migriert diff --git a/UPGRADE.md b/UPGRADE.md index df5a396fe..30b62d959 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,6 @@ +# 14.0.4 +- Media processing has been simplified. It's no longer done in parallel messages. The migration UI reflects the media migration progress more accurately. When the step is marked as complete, all media are migrated. No need to wait for additional messages to finish. + # 14.0.0 - [BREAKING] MIG-1053 - Removed ability to set the `verify` flag for the guzzle API client. This is now always true by default. - [BREAKING] MIG-1053 - Refactored both Shopware 5 and Shopware 6 EnvironmentReader classes to provide more information about exceptions. diff --git a/composer.json b/composer.json index 575018f94..91561738b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "swag/migration-assistant", "description": "Migration plugin for shopware/platform", - "version": "15.0.3", + "version": "15.0.4", "type": "shopware-platform-plugin", "license": "MIT", "authors": [