diff --git a/src/DataProvider/Provider/Data/ProductProvider.php b/src/DataProvider/Provider/Data/ProductProvider.php index 15f279c38..b775cca93 100644 --- a/src/DataProvider/Provider/Data/ProductProvider.php +++ b/src/DataProvider/Provider/Data/ProductProvider.php @@ -7,10 +7,13 @@ namespace SwagMigrationAssistant\DataProvider\Provider\Data; +use Doctrine\DBAL\Connection; use Shopware\Core\Content\Product\ProductCollection; 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\NotFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; @@ -20,12 +23,17 @@ #[Package('fundamentals@after-sales')] class ProductProvider extends AbstractProvider { + private const BUNDLE_PRODUCT_TYPE = 'grouped_bundle'; + + private ?bool $hasTypeColumn = null; + /** * @param EntityRepository $productRepo */ public function __construct( private readonly EntityRepository $productRepo, private readonly RouterInterface $router, + private readonly Connection $connection, ) { } @@ -55,6 +63,7 @@ public function getProvidedData(int $limit, int $offset, Context $context): arra new FieldSorting('parentId'), // get 'NULL' parentIds first new FieldSorting('id') ); + $this->addBundleExclusionFilter($criteria); $result = $this->productRepo->search($criteria, $context); $cleanResult = $this->cleanupSearchResult($result, [ @@ -113,6 +122,34 @@ public function getProvidedData(int $limit, int $offset, Context $context): arra public function getProvidedTotal(Context $context): int { - return $this->readTotalFromRepo($this->productRepo, $context); + $criteria = new Criteria(); + $this->addBundleExclusionFilter($criteria); + + return $this->readTotalFromRepo($this->productRepo, $context, $criteria); + } + + private function addBundleExclusionFilter(Criteria $criteria): void + { + if (!$this->hasTypeColumn()) { + return; + } + + $criteria->addFilter( + new NotFilter(NotFilter::CONNECTION_AND, [ + new EqualsFilter('type', self::BUNDLE_PRODUCT_TYPE), + ]) + ); + } + + private function hasTypeColumn(): bool + { + if ($this->hasTypeColumn !== null) { + return $this->hasTypeColumn; + } + + $columns = $this->connection->createSchemaManager()->listTableColumns('product'); + $this->hasTypeColumn = isset($columns['type']); + + return $this->hasTypeColumn; } } diff --git a/src/DependencyInjection/dataProvider.xml b/src/DependencyInjection/dataProvider.xml index 664bee350..82e830529 100644 --- a/src/DependencyInjection/dataProvider.xml +++ b/src/DependencyInjection/dataProvider.xml @@ -71,6 +71,7 @@ + diff --git a/src/DependencyInjection/shopware6.xml b/src/DependencyInjection/shopware6.xml index a5f3e65ba..79c2781ab 100644 --- a/src/DependencyInjection/shopware6.xml +++ b/src/DependencyInjection/shopware6.xml @@ -115,6 +115,7 @@ + diff --git a/src/Profile/Shopware/Converter/AttributeConverter.php b/src/Profile/Shopware/Converter/AttributeConverter.php index 629301665..2f50edb16 100644 --- a/src/Profile/Shopware/Converter/AttributeConverter.php +++ b/src/Profile/Shopware/Converter/AttributeConverter.php @@ -46,6 +46,14 @@ public function convert(array $data, Context $context, MigrationContextInterface $this->connectionName = $connection->getName(); } + $existingCustomFieldSetMapping = $this->mappingService->getMapping( + $this->connectionId, + DefaultEntities::CUSTOM_FIELD_SET, + $this->getCustomFieldEntityName() . 'CustomFieldSet', + $context + ); + $isCustomFieldSetUpdate = $existingCustomFieldSetMapping !== null; + $mapping = $this->mappingService->getOrCreateMapping( $this->connectionId, DefaultEntities::CUSTOM_FIELD_SET, @@ -57,7 +65,12 @@ public function convert(array $data, Context $context, MigrationContextInterface $connectionName = ConnectionNameSanitizer::sanitize($this->connectionName); - $converted['name'] = 'migration_' . $connectionName . '_' . $this->getCustomFieldEntityName(); + $customFieldSetName = 'migration_' . $connectionName . '_' . $this->getCustomFieldEntityName(); + + if (!$isCustomFieldSetUpdate) { + $converted['name'] = $customFieldSetName; + } + $converted['config'] = [ 'label' => [ $data['_locale'] => \ucfirst($this->getCustomFieldEntityName()) . ' migration custom fields (attributes)', @@ -84,6 +97,14 @@ public function convert(array $data, Context $context, MigrationContextInterface $additionalData['columnType'] = $data['configuration']['column_type']; } + $existingCustomFieldMapping = $this->mappingService->getMapping( + $this->connectionId, + $this->getCustomFieldEntityName(), + $data['name'], + $context + ); + $isCustomFieldUpdate = $existingCustomFieldMapping !== null; + $this->mainMapping = $this->mappingService->getOrCreateMapping( $this->connectionId, $this->getCustomFieldEntityName(), @@ -93,15 +114,18 @@ public function convert(array $data, Context $context, MigrationContextInterface $additionalData ); - $converted['customFields'] = [ - [ - 'id' => $this->mainMapping['entityUuid'], - 'name' => $converted['name'] . '_' . $data['name'], - 'type' => $this->getCustomFieldType($data), - 'config' => $this->getCustomFieldConfiguration($data), - ], + $customField = [ + 'id' => $this->mainMapping['entityUuid'], + 'config' => $this->getCustomFieldConfiguration($data), ]; + if (!$isCustomFieldUpdate) { + $customField['name'] = $customFieldSetName . '_' . $data['name']; + $customField['type'] = $this->getCustomFieldType($data); + } + + $converted['customFields'] = [$customField]; + unset( $data['name'], $data['type'], diff --git a/src/Profile/Shopware6/Converter/CustomFieldSetConverter.php b/src/Profile/Shopware6/Converter/CustomFieldSetConverter.php index 121828820..b2e2a7215 100644 --- a/src/Profile/Shopware6/Converter/CustomFieldSetConverter.php +++ b/src/Profile/Shopware6/Converter/CustomFieldSetConverter.php @@ -27,12 +27,43 @@ protected function convertData(array $data): ConvertStruct { $converted = $data; + // Check if custom field set already exists (was previously migrated) before creating mapping + // This is needed because the 'name' field has Immutable flag and can only be set on create + $existingMapping = $this->mappingService->getMapping( + $this->connectionId, + DefaultEntities::CUSTOM_FIELD_SET, + $data['id'], + $this->context + ); + $isUpdate = $existingMapping !== null; + $this->mainMapping = $this->getOrCreateMappingMainCompleteFacade( DefaultEntities::CUSTOM_FIELD_SET, $data['id'], $converted['id'] ); + $this->removeImmutableFields($converted, $isUpdate); + return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); } + + /** + * @param array $converted + */ + private function removeImmutableFields(array &$converted, bool $isUpdate): void + { + if (!$isUpdate) { + return; + } + + unset($converted['name']); + + if (isset($converted['customFields']) && \is_array($converted['customFields'])) { + foreach ($converted['customFields'] as &$customField) { + unset($customField['name'], $customField['type']); + } + unset($customField); + } + } } diff --git a/src/Profile/Shopware6/Converter/ProductConverter.php b/src/Profile/Shopware6/Converter/ProductConverter.php index 60494ae22..6af0c0e80 100644 --- a/src/Profile/Shopware6/Converter/ProductConverter.php +++ b/src/Profile/Shopware6/Converter/ProductConverter.php @@ -7,10 +7,15 @@ namespace SwagMigrationAssistant\Profile\Shopware6\Converter; +use Doctrine\DBAL\Connection; +use Shopware\Core\Content\Product\ProductDefinition; use Shopware\Core\Defaults; use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\Converter\ConvertStruct; use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; +use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; +use SwagMigrationAssistant\Migration\Mapping\MappingServiceInterface; +use SwagMigrationAssistant\Migration\Media\MediaFileServiceInterface; use SwagMigrationAssistant\Migration\MigrationContextInterface; use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\ProductDataSet; use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; @@ -20,6 +25,17 @@ class ProductConverter extends ShopwareMediaConverter { private ?string $sourceDefaultCurrencyUuid; + private ?bool $hasTypeColumn = null; + + public function __construct( + MappingServiceInterface $mappingService, + LoggingServiceInterface $loggingService, + MediaFileServiceInterface $mediaFileService, + private readonly Connection $connection, + ) { + parent::__construct($mappingService, $loggingService, $mediaFileService); + } + public function supports(MigrationContextInterface $migrationContext): bool { return $migrationContext->getProfile()->getName() === Shopware6MajorProfile::PROFILE_NAME @@ -58,6 +74,16 @@ protected function convertData(array $data): ConvertStruct { $converted = $data; + // Check if product already exists (was previously migrated) before creating mapping + // This is needed because the 'type' field has Immutable flag and can only be set on create + $existingMapping = $this->mappingService->getMapping( + $this->connectionId, + DefaultEntities::PRODUCT, + $data['id'], + $this->context + ); + $isUpdate = $existingMapping !== null; + $this->mainMapping = $this->getOrCreateMappingMainCompleteFacade( DefaultEntities::PRODUCT, $data['id'], @@ -205,6 +231,8 @@ protected function convertData(array $data): ConvertStruct ); } + $this->convertStatesToType($converted, $isUpdate); + return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); } @@ -235,4 +263,46 @@ private function checkDefaultCurrency(array &$source, string $key): void $source[$key][] = $defaultPrice; } } + + /** + * @param array $converted + */ + private function convertStatesToType(array &$converted, bool $isUpdate): void + { + if (!$this->hasTypeColumn()) { + return; + } + + if ($isUpdate) { + unset($converted['type']); + + return; + } + + if (isset($converted['type'])) { + return; + } + + if (isset($converted['states']) && \is_array($converted['states'])) { + $converted['type'] = \in_array('is-download', $converted['states'], true) + ? ProductDefinition::TYPE_DIGITAL + : ProductDefinition::TYPE_PHYSICAL; + + return; + } + + $converted['type'] = ProductDefinition::TYPE_PHYSICAL; + } + + private function hasTypeColumn(): bool + { + if ($this->hasTypeColumn !== null) { + return $this->hasTypeColumn; + } + + $columns = $this->connection->createSchemaManager()->listTableColumns('product'); + $this->hasTypeColumn = isset($columns['type']); + + return $this->hasTypeColumn; + } } diff --git a/tests/Profile/Shopware6/Converter/ProductConverterTest.php b/tests/Profile/Shopware6/Converter/ProductConverterTest.php index 7b1d41b91..f5dd2e764 100644 --- a/tests/Profile/Shopware6/Converter/ProductConverterTest.php +++ b/tests/Profile/Shopware6/Converter/ProductConverterTest.php @@ -7,6 +7,10 @@ namespace SwagMigrationAssistant\Test\Profile\Shopware6\Converter; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Types\StringType; use Shopware\Core\Framework\Log\Package; use SwagMigrationAssistant\Migration\Converter\ConverterInterface; use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSet; @@ -25,7 +29,15 @@ protected function createConverter( MediaFileServiceInterface $mediaFileService, ?array $mappingArray = [], ): ConverterInterface { - return new ProductConverter($mappingService, $loggingService, $mediaFileService); + $schemaManager = $this->createMock(AbstractSchemaManager::class); + $schemaManager->method('listTableColumns')->willReturn([ + 'type' => new Column('type', new StringType()), + ]); + + $connection = $this->createMock(Connection::class); + $connection->method('createSchemaManager')->willReturn($schemaManager); + + return new ProductConverter($mappingService, $loggingService, $mediaFileService, $connection); } protected function createDataSet(): DataSet diff --git a/tests/_fixtures/Shopware6/Product/01-HappyCaseContainer/output.php b/tests/_fixtures/Shopware6/Product/01-HappyCaseContainer/output.php index f2d2a1061..3cdea7302 100644 --- a/tests/_fixtures/Shopware6/Product/01-HappyCaseContainer/output.php +++ b/tests/_fixtures/Shopware6/Product/01-HappyCaseContainer/output.php @@ -56,4 +56,5 @@ 'id' => 'd7e9ceac19a948abad07667419424b13', ], ], + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/02-HappyCaseVariant/output.php b/tests/_fixtures/Shopware6/Product/02-HappyCaseVariant/output.php index 9524f34b1..41c1353d1 100644 --- a/tests/_fixtures/Shopware6/Product/02-HappyCaseVariant/output.php +++ b/tests/_fixtures/Shopware6/Product/02-HappyCaseVariant/output.php @@ -22,4 +22,5 @@ 'id' => 'bfaf0c7366e6454fb7516ab47435b01a', ], ], + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/03-MissingManufacturerMapping/output.php b/tests/_fixtures/Shopware6/Product/03-MissingManufacturerMapping/output.php index dc71522c5..24fa92547 100644 --- a/tests/_fixtures/Shopware6/Product/03-MissingManufacturerMapping/output.php +++ b/tests/_fixtures/Shopware6/Product/03-MissingManufacturerMapping/output.php @@ -55,4 +55,5 @@ 'id' => 'd7e9ceac19a948abad07667419424b13', ], ], + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/04-StripUnmigrateableData/output.php b/tests/_fixtures/Shopware6/Product/04-StripUnmigrateableData/output.php index 99881960a..f758739e1 100644 --- a/tests/_fixtures/Shopware6/Product/04-StripUnmigrateableData/output.php +++ b/tests/_fixtures/Shopware6/Product/04-StripUnmigrateableData/output.php @@ -57,4 +57,5 @@ 'id' => 'd7e9ceac19a948abad07667419424b13', ], ], + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/05-AdvancedPrices/output.php b/tests/_fixtures/Shopware6/Product/05-AdvancedPrices/output.php index 60f53daaf..d1824b9c5 100644 --- a/tests/_fixtures/Shopware6/Product/05-AdvancedPrices/output.php +++ b/tests/_fixtures/Shopware6/Product/05-AdvancedPrices/output.php @@ -139,4 +139,5 @@ ], 'coverId' => 'bdeb106f47ab4255b2bd5f35d84cae7c', 'id' => 'fb2dbbee297c472c9e916b26952615ff', + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/06-DeliveryTimes/output.php b/tests/_fixtures/Shopware6/Product/06-DeliveryTimes/output.php index b934b0764..ab2403e38 100644 --- a/tests/_fixtures/Shopware6/Product/06-DeliveryTimes/output.php +++ b/tests/_fixtures/Shopware6/Product/06-DeliveryTimes/output.php @@ -140,4 +140,5 @@ 'coverId' => 'bdeb106f47ab4255b2bd5f35d84cae7c', 'deliveryTimeId' => 'bdeb106f47ab4255b2bd5f35d84cae7c', 'id' => 'fb2dbbee297c472c9e916b26952615ff', + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/07-Media/output.php b/tests/_fixtures/Shopware6/Product/07-Media/output.php index 455769093..8776a39f4 100644 --- a/tests/_fixtures/Shopware6/Product/07-Media/output.php +++ b/tests/_fixtures/Shopware6/Product/07-Media/output.php @@ -83,4 +83,5 @@ 'coverId' => 'eb5483a9c77c4919b5d110e8d745a1cc', 'deliveryTimeId' => 'bdeb106f47ab4255b2bd5f35d84cae7c', 'id' => 'fb2dbbee297c472c9e916b26952615ff', + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/08-MigrateWithoutDefaultCurrency/output.php b/tests/_fixtures/Shopware6/Product/08-MigrateWithoutDefaultCurrency/output.php index 43d425779..9b87fed4c 100644 --- a/tests/_fixtures/Shopware6/Product/08-MigrateWithoutDefaultCurrency/output.php +++ b/tests/_fixtures/Shopware6/Product/08-MigrateWithoutDefaultCurrency/output.php @@ -209,4 +209,5 @@ 'id' => 'd7e9ceac19a948abad07667419424b13', ], ], + 'type' => 'physical', ]; diff --git a/tests/_fixtures/Shopware6/Product/09-MigrateWithConfiguratorSettings/output.php b/tests/_fixtures/Shopware6/Product/09-MigrateWithConfiguratorSettings/output.php index 37bb6fc03..165f24869 100644 --- a/tests/_fixtures/Shopware6/Product/09-MigrateWithConfiguratorSettings/output.php +++ b/tests/_fixtures/Shopware6/Product/09-MigrateWithConfiguratorSettings/output.php @@ -121,4 +121,5 @@ 'coverId' => 'eb5483a9c77c4919b5d110e8d745a1cc', 'deliveryTimeId' => 'bdeb106f47ab4255b2bd5f35d84cae7c', 'id' => 'fb2dbbee297c472c9e916b26952615ff', + 'type' => 'physical', ];