diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98f3514..8226b6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,10 +64,6 @@ jobs: name: Run PHPStan run: vendor/bin/phpstan analyse -c phpstan.neon -l max src/ - - - name: Run Psalm - run: vendor/bin/psalm - - name: Run PHPSpec run: vendor/bin/phpspec run --ansi -f progress --no-interaction @@ -151,10 +147,6 @@ jobs: name: Run PHPStan run: vendor/bin/phpstan analyse -c phpstan.neon -l max src/ - - - name: Run Psalm - run: vendor/bin/psalm - - name: Run PHPSpec run: vendor/bin/phpspec run --ansi -f progress --no-interaction diff --git a/composer.json b/composer.json index 6aaead7..d2ef34c 100644 --- a/composer.json +++ b/composer.json @@ -44,8 +44,7 @@ "symfony/dotenv": "^5.4 || ^6.0", "symfony/flex": "^2.2.2", "symfony/intl": "^5.4 || ^6.0", - "symfony/web-profiler-bundle": "^5.4 || ^6.0", - "vimeo/psalm": "5.25.0" + "symfony/web-profiler-bundle": "^5.4 || ^6.0" }, "conflict": { "behat/mink-selenium2-driver": ">=1.7.0" @@ -94,6 +93,15 @@ "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", "security-checker security:check": "script" - } + }, + "analyse": [ + "composer validate --ansi --strict", + "vendor/bin/phpstan analyse -c phpstan.neon -l max src/" + ], + "test": [ + "vendor/bin/phpspec run --ansi -f progress --no-interaction", + "vendor/bin/phpunit --colors=always", + "vendor/bin/behat --colors --strict -vvv --no-interaction || vendor/bin/behat --colors --strict -vvv --no-interaction --rerun" + ] } } diff --git a/config/app/config.yaml b/config/app/config.yaml index 71be874..e1de9ac 100644 --- a/config/app/config.yaml +++ b/config/app/config.yaml @@ -5,3 +5,8 @@ sylius_ui: commerce_weavers_sylius_also_bought_missing_configuration: template: '@CommerceWeaversSyliusAlsoBoughtPlugin/_missing_configuration.html.twig' priority: 40 + sylius.admin.channel.form.second_column_content: + blocks: + commerce_weavers_sylius_also_bought_number_of_synchronised_products: + template: '@CommerceWeaversSyliusAlsoBoughtPlugin/Admin/Channel/Form/numberOfSynchronisedProducts.html.twig' + priority: 0 diff --git a/config/services/form.xml b/config/services/form.xml new file mode 100644 index 0000000..30b3a5b --- /dev/null +++ b/config/services/form.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/config/services/processor.xml b/config/services/processor.xml index b665763..0db467e 100644 --- a/config/services/processor.xml +++ b/config/services/processor.xml @@ -10,7 +10,7 @@ - %commerce_weavers_sylius_also_bought.number_of_products_to_associate% + diff --git a/config/services/provider.xml b/config/services/provider.xml index b0d7e98..2b3a926 100644 --- a/config/services/provider.xml +++ b/config/services/provider.xml @@ -27,5 +27,12 @@ > + + + %commerce_weavers_sylius_also_bought.number_of_products_to_associate% + diff --git a/features/managing_number_of_items_to_synchronize_on_channel.feature b/features/managing_number_of_items_to_synchronize_on_channel.feature new file mode 100644 index 0000000..29e6fc5 --- /dev/null +++ b/features/managing_number_of_items_to_synchronize_on_channel.feature @@ -0,0 +1,22 @@ +@managing_channels +Feature: Managing number of items to synchronize on channel + In order to change the number of items that are synchronised as bought together products + As an Administrator + I want to be able to edit their number on channel configuration + + Background: + Given the store operates on a channel named "Web Channel" + And I am logged in as an administrator + + @ui + Scenario: Seeing the default number of synchronised products + When I want to modify a channel "Web Channel" + Then the number of synchronised products should be 10 + + @ui + Scenario: Modifying the number of synchronised products + When I want to modify a channel "Web Channel" + And I change the number of synchronised products to 5 + And I save my changes + Then I should be notified that it has been successfully edited + And the number of synchronised products should be 5 diff --git a/features/synchronizing_bought_together_products.feature b/features/synchronizing_bought_together_products.feature index eac6089..4f49cb5 100644 --- a/features/synchronizing_bought_together_products.feature +++ b/features/synchronizing_bought_together_products.feature @@ -41,6 +41,39 @@ Feature: Synchronizing bought together products And the "Grains" product should have usually been bought with "Twigs", "Bird bath", "Bird netting", "Weaver" and "Swan" And the "Bird feeder" product should have usually been bought with "Grains", "Twigs", "Bird bath", "Bird netting" and "Swift" + @cli + Scenario: Synchronizing maximum number of bought together products + Given the channel "United States" has 2 configured as number of synchronizable bought together products + And the store has a product association type "Bought together" with a code "bought_together" + And the store has products "Weaver", "Swan", "Swift", "Grains", "Twigs", "Bird feeder", "Bird bath" and "Bird netting" + And there is a customer "john.doe@example.com" that placed an order + And the customer bought a single "Weaver", "Bird bath" and "Bird netting" + And the customer bought 100 "Grains" products + And the customer bought 500 "Twigs" products + And the customer "John Doe" addressed it to "Elm Street", "90802" "Anytown" in the "United States" with identical billing address + And the customer chose "Free" shipping method with "Cash on delivery" payment + And this order is already paid + And there is another customer "john.galt@example.com" that placed an order + And the customer bought a single "Swan" + And the customer bought 200 "Grains" products + And the customer bought 20 "Twigs" products + And the customer "John Galt" addressed it to "Atlas Way", "385" "Libertyville" in the "United States" with identical billing address + And the customer chose "Free" shipping method with "Cash on delivery" payment + And this order is already paid + And there is another customer "jane.doe@example.com" that placed an order + And the customer bought a single "Swift", "Bird feeder", "Bird bath" and "Bird netting" + And the customer bought 100 "Grains" products + And the customer bought 100 "Twigs" products + And the customer "Jane Doe" addressed it to "Elm Street", "90802" "Anytown" in the "United States" with identical billing address + And the customer chose "Free" shipping method with "Cash on delivery" payment + And this order is already paid + When I synchronize bought together products by running command + Then I should be informed that the bought together products are synchronized + And the "Weaver" product should have usually been bought with "Grains" and "Twigs" + And the "Swan" product should have usually been bought with "Grains" and "Twigs" + And the "Grains" product should have usually been bought with "Twigs" and "Bird bath" + And the "Bird feeder" product should have usually been bought with "Grains" and "Twigs" + @cli Scenario: Being notified about specific association type requirement Given the store has a product "Weaver" diff --git a/migrations/Version20240408131819.php b/migrations/Version20240408131819.php new file mode 100644 index 0000000..202f6c0 --- /dev/null +++ b/migrations/Version20240408131819.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE sylius_channel ADD number_of_synchronised_products INT DEFAULT 10 NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE sylius_channel DROP number_of_synchronised_products'); + } +} diff --git a/phpstan.neon b/phpstan.neon index 5d15c5a..181059c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,7 @@ parameters: - src - tests/Behat - excludes_analyse: + excludePaths: # Makes PHPStan crash - 'src/DependencyInjection/Configuration.php' diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 9aeae74..0000000 --- a/psalm.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php b/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php index 950bdf2..ed66978 100644 --- a/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php +++ b/src/DependencyInjection/CommerceWeaversSyliusAlsoBoughtExtension.php @@ -18,7 +18,6 @@ final class CommerceWeaversSyliusAlsoBoughtExtension extends AbstractResourceExt private const ALIAS = 'commerce_weavers_sylius_also_bought'; - /** @psalm-suppress UnusedVariable */ public function load(array $configs, ContainerBuilder $container): void { $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index aec7c25..72def83 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -15,7 +15,6 @@ final class Configuration implements ConfigurationInterface { - /** @psalm-suppress UnusedVariable */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('commerce_weavers_sylius_also_bought'); diff --git a/src/Entity/BoughtTogetherConfigurableChannelInterface.php b/src/Entity/BoughtTogetherConfigurableChannelInterface.php new file mode 100644 index 0000000..0ba99f0 --- /dev/null +++ b/src/Entity/BoughtTogetherConfigurableChannelInterface.php @@ -0,0 +1,12 @@ + 10])] + private int $numberOfSynchronisedProducts = 10; + + public function setNumberOfSynchronisedProducts(int $number): void + { + $this->numberOfSynchronisedProducts = $number; + } + + public function getNumberOfSynchronisedProducts(): int + { + return $this->numberOfSynchronisedProducts; + } +} diff --git a/src/Form/Extension/ChannelTypeExtension.php b/src/Form/Extension/ChannelTypeExtension.php new file mode 100644 index 0000000..7ab21c9 --- /dev/null +++ b/src/Form/Extension/ChannelTypeExtension.php @@ -0,0 +1,28 @@ +add('numberOfSynchronisedProducts', NumberType::class, [ + 'label' => 'commerce_weavers_sylius_also_bought.form.channel.number_of_synchronised_products', + 'required' => true, + ]) + ; + } + + public static function getExtendedTypes(): iterable + { + yield ChannelType::class; + } +} diff --git a/src/Processor/BoughtTogetherProductsAssociationProcessor.php b/src/Processor/BoughtTogetherProductsAssociationProcessor.php index 4e4490b..6da3172 100644 --- a/src/Processor/BoughtTogetherProductsAssociationProcessor.php +++ b/src/Processor/BoughtTogetherProductsAssociationProcessor.php @@ -7,6 +7,7 @@ use CommerceWeavers\SyliusAlsoBoughtPlugin\Entity\BoughtTogetherProductsAwareInterface; use CommerceWeavers\SyliusAlsoBoughtPlugin\Event\SynchronizationEnded; use CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\BoughtTogetherProductsAssociationProviderInterface; +use CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\SynchronizableProductsNumberProviderInterface; use Doctrine\ORM\EntityManagerInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Repository\ProductRepositoryInterface; @@ -20,7 +21,8 @@ public function __construct( private EntityManagerInterface $entityManager, private ProductRepositoryInterface $productRepository, private BoughtTogetherProductsAssociationProviderInterface $boughtTogetherProductsAssociationProvider, - private int $numberOfProductsToAssociate = 10, + private SynchronizableProductsNumberProviderInterface $synchronizableProductsNumberProvider, + private int $batchSize = 100, ) { } @@ -31,8 +33,14 @@ public function __invoke(SynchronizationEnded $event): void Assert::allIsInstanceOf($products, BoughtTogetherProductsAwareInterface::class); + $batch = 0; foreach ($products as $product) { - $boughtTogetherProducts = array_slice($product->getBoughtTogetherProducts(), 0, $this->numberOfProductsToAssociate - 1, true); + $boughtTogetherProducts = array_slice( + $product->getBoughtTogetherProducts(), + 0, + $this->synchronizableProductsNumberProvider->getNumberOfProductsToSynchronise($product) - 1, + true, + ); $boughtTogetherAssociation = $this->boughtTogetherProductsAssociationProvider->getForProduct($product); $this->entityManager->persist($boughtTogetherAssociation); @@ -42,14 +50,17 @@ public function __invoke(SynchronizationEnded $event): void foreach ($frequentlyBoughtTogetherProducts as $frequentlyBoughtTogetherProduct) { $boughtTogetherAssociation->addAssociatedProduct($frequentlyBoughtTogetherProduct); } + + if (++$batch >= $this->batchSize) { + $this->entityManager->flush(); + $batch = 0; + } } $this->entityManager->flush(); } - /** - * @return ProductInterface[] - */ + /** @return ProductInterface[] */ private function findProductsByCodes(array $codes): array { /** @var ProductInterface[] $products */ diff --git a/src/Provider/SynchronizableProductsNumberProvider.php b/src/Provider/SynchronizableProductsNumberProvider.php new file mode 100644 index 0000000..3ab6e40 --- /dev/null +++ b/src/Provider/SynchronizableProductsNumberProvider.php @@ -0,0 +1,32 @@ +getChannels(); + + $numberOfProducts = 0; + /** @var BoughtTogetherConfigurableChannelInterface $channel */ + foreach ($channels as $channel) { + $numberOfProducts = max($numberOfProducts, $channel->getNumberOfSynchronisedProducts()); + } + + if ($numberOfProducts <= 0) { + return $this->defaultNumberOfProducts; + } + + return $numberOfProducts; + } +} diff --git a/src/Provider/SynchronizableProductsNumberProviderInterface.php b/src/Provider/SynchronizableProductsNumberProviderInterface.php new file mode 100644 index 0000000..d088aa4 --- /dev/null +++ b/src/Provider/SynchronizableProductsNumberProviderInterface.php @@ -0,0 +1,12 @@ + +
+ {{ form_row(form.numberOfSynchronisedProducts) }} +
diff --git a/tests/Application/config/packages/_sylius.yaml b/tests/Application/config/packages/_sylius.yaml index 4646286..5c4ac11 100644 --- a/tests/Application/config/packages/_sylius.yaml +++ b/tests/Application/config/packages/_sylius.yaml @@ -6,7 +6,7 @@ imports: - { resource: "@SyliusShopBundle/Resources/config/app/config.yml" } - { resource: "@SyliusApiBundle/Resources/config/app/config.yaml" } - + - { resource: "@CommerceWeaversSyliusAlsoBoughtPlugin/config/app/config.yaml" } parameters: @@ -18,9 +18,15 @@ sylius_shop: sylius_api: enabled: true - + sylius_product: resources: product: classes: model: Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Application\Entity\Product + +sylius_channel: + resources: + channel: + classes: + model: Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Application\Entity\Channel diff --git a/tests/Application/src/Entity/Channel.php b/tests/Application/src/Entity/Channel.php new file mode 100644 index 0000000..b2e63e2 --- /dev/null +++ b/tests/Application/src/Entity/Channel.php @@ -0,0 +1,17 @@ +setNumberOfSynchronisedProducts($number); + + $this->entityManager->flush(); + } +} diff --git a/tests/Behat/Context/Ui/Admin/ManagingChannelsContext.php b/tests/Behat/Context/Ui/Admin/ManagingChannelsContext.php new file mode 100644 index 0000000..c544142 --- /dev/null +++ b/tests/Behat/Context/Ui/Admin/ManagingChannelsContext.php @@ -0,0 +1,32 @@ +channelFormElement->changeNumberOfSynchronisedProducts($number); + } + + /** + * @Then the number of synchronised products should be :numner + */ + public function theNumberOfSynchronisedProductsShouldBe(int $number): void + { + Assert::same($number, $this->channelFormElement->getNumberOfSynchronisedProducts()); + } +} diff --git a/tests/Behat/Element/Admin/ChannelFormElement.php b/tests/Behat/Element/Admin/ChannelFormElement.php new file mode 100644 index 0000000..385372d --- /dev/null +++ b/tests/Behat/Element/Admin/ChannelFormElement.php @@ -0,0 +1,27 @@ +getElement('number_of_synchronised_products_input')->setValue($number); + } + + public function getNumberOfSynchronisedProducts(): int + { + return (int) $this->getElement('number_of_synchronised_products_input')->getValue(); + } + + protected function getDefinedElements(): array + { + return array_merge(parent::getDefinedElements(), [ + 'number_of_synchronised_products_input' => 'input[name="sylius_channel[numberOfSynchronisedProducts]"]', + ]); + } +} diff --git a/tests/Behat/Element/Admin/ChannelFormElementInterface.php b/tests/Behat/Element/Admin/ChannelFormElementInterface.php new file mode 100644 index 0000000..538d539 --- /dev/null +++ b/tests/Behat/Element/Admin/ChannelFormElementInterface.php @@ -0,0 +1,12 @@ + + + + + diff --git a/tests/Behat/Resources/services/contexts/ui.xml b/tests/Behat/Resources/services/contexts/ui.xml index 1845ac0..17ef289 100644 --- a/tests/Behat/Resources/services/contexts/ui.xml +++ b/tests/Behat/Resources/services/contexts/ui.xml @@ -4,6 +4,10 @@ + + + + diff --git a/tests/Behat/Resources/services/elements/admin.xml b/tests/Behat/Resources/services/elements/admin.xml index 57423a2..c76714c 100644 --- a/tests/Behat/Resources/services/elements/admin.xml +++ b/tests/Behat/Resources/services/elements/admin.xml @@ -7,5 +7,11 @@ class="Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Element\Admin\MissingConfigurationErrorMessageElement" parent="sylius.behat.element" /> + + diff --git a/tests/Behat/Resources/suites.yml b/tests/Behat/Resources/suites.yml index a8ffb99..235522b 100644 --- a/tests/Behat/Resources/suites.yml +++ b/tests/Behat/Resources/suites.yml @@ -1,5 +1,6 @@ imports: - suites/api/viewing_products.yaml - suites/cli/also_bought.yaml + - suites/ui/managing_channels.yaml - suites/ui/viewing_error_message.yaml - suites/ui/viewing_products.yaml diff --git a/tests/Behat/Resources/suites/cli/also_bought.yaml b/tests/Behat/Resources/suites/cli/also_bought.yaml index 7df0f08..3424012 100644 --- a/tests/Behat/Resources/suites/cli/also_bought.yaml +++ b/tests/Behat/Resources/suites/cli/also_bought.yaml @@ -3,17 +3,18 @@ default: cli_also_bought: contexts: - sylius.behat.context.hook.doctrine_orm - + - sylius.behat.context.setup.channel - sylius.behat.context.setup.geographical - sylius.behat.context.setup.order - sylius.behat.context.setup.payment - sylius.behat.context.setup.product - - sylius.behat.context.setup.product_association + - sylius.behat.context.setup.product_association - sylius.behat.context.setup.shipping + - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Setup\ChannelContext - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Setup\OrderContext - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Setup\ProductContext - + - sylius.behat.context.transform.address - sylius.behat.context.transform.channel - sylius.behat.context.transform.customer @@ -22,7 +23,7 @@ default: - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.shipping_method - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Transform\ProductContext - + - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Cli\CreateBoughtTogetherProductAssociationTypeContext - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Cli\SynchronizeBoughtTogetherProductsContext filters: diff --git a/tests/Behat/Resources/suites/ui/managing_channels.yaml b/tests/Behat/Resources/suites/ui/managing_channels.yaml new file mode 100644 index 0000000..b581b31 --- /dev/null +++ b/tests/Behat/Resources/suites/ui/managing_channels.yaml @@ -0,0 +1,33 @@ +default: + suites: + ui_managing_channels: + contexts: + - sylius.behat.context.hook.doctrine_orm + - sylius.behat.context.hook.session + + - sylius.behat.context.transform.address + - sylius.behat.context.transform.channel + - sylius.behat.context.transform.country + - sylius.behat.context.transform.currency + - sylius.behat.context.transform.locale + - sylius.behat.context.transform.shared_storage + - sylius.behat.context.transform.taxon + - sylius.behat.context.transform.zone + + - sylius.behat.context.setup.channel + - sylius.behat.context.setup.currency + - sylius.behat.context.setup.geographical + - sylius.behat.context.setup.locale + - sylius.behat.context.setup.payment + - sylius.behat.context.setup.admin_security + - sylius.behat.context.setup.shipping + - sylius.behat.context.setup.taxonomy + - sylius.behat.context.setup.zone + + - sylius.behat.context.ui.admin.managing_channels + - sylius.behat.context.ui.admin.managing_channels_billing_data + - sylius.behat.context.ui.admin.notification + + - Tests\CommerceWeavers\SyliusAlsoBoughtPlugin\Behat\Context\Ui\Admin\ManagingChannelsContext + filters: + tags: "@managing_channels&&@ui" diff --git a/tests/Unit/Checker/BoughtTogetherProductAssociationTypeConfiguredCheckerTest.php b/tests/Unit/Checker/BoughtTogetherProductAssociationTypeConfiguredCheckerTest.php index 1f9e30f..ae443d0 100644 --- a/tests/Unit/Checker/BoughtTogetherProductAssociationTypeConfiguredCheckerTest.php +++ b/tests/Unit/Checker/BoughtTogetherProductAssociationTypeConfiguredCheckerTest.php @@ -33,7 +33,7 @@ public function testItChecksIfBoughtTogetherProductAssociationTypeIsConfigured(b self::assertSame($isConfigured, $checker->isConfigured()); } - public function isConfiguredDataProvider(): array + public static function isConfiguredDataProvider(): array { return [ 'association type is configured' => [ diff --git a/tests/Unit/Processor/BoughtTogetherProductsAssociationProcessorTest.php b/tests/Unit/Processor/BoughtTogetherProductsAssociationProcessorTest.php index b90b573..fa09cc5 100644 --- a/tests/Unit/Processor/BoughtTogetherProductsAssociationProcessorTest.php +++ b/tests/Unit/Processor/BoughtTogetherProductsAssociationProcessorTest.php @@ -7,6 +7,7 @@ use CommerceWeavers\SyliusAlsoBoughtPlugin\Event\SynchronizationEnded; use CommerceWeavers\SyliusAlsoBoughtPlugin\Processor\BoughtTogetherProductsAssociationProcessor; use CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\BoughtTogetherProductsAssociationProviderInterface; +use CommerceWeavers\SyliusAlsoBoughtPlugin\Provider\SynchronizableProductsNumberProviderInterface; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -24,12 +25,13 @@ public function testProcessingBoughtTogetherProductsIntoAssociation(): void $entityManager = $this->prophesize(EntityManagerInterface::class); $productRepository = $this->prophesize(ProductRepositoryInterface::class); $boughtTogetherProductsAssociationProvider = $this->prophesize(BoughtTogetherProductsAssociationProviderInterface::class); + $numberOfSynchronizableProductsProvider = $this->prophesize(SynchronizableProductsNumberProviderInterface::class); $processor = new BoughtTogetherProductsAssociationProcessor( $entityManager->reveal(), $productRepository->reveal(), $boughtTogetherProductsAssociationProvider->reveal(), - 5 + $numberOfSynchronizableProductsProvider->reveal(), ); $product = $this->prophesize(Product::class); @@ -38,6 +40,8 @@ public function testProcessingBoughtTogetherProductsIntoAssociation(): void $boughtTogetherProductsAssociationProvider->getForProduct($product->reveal())->willReturn($productAssociation); $product->getBoughtTogetherProducts()->willReturn(['12345' => 2, '500' => 1]); + $numberOfSynchronizableProductsProvider->getNumberOfProductsToSynchronise($product->reveal())->willReturn(10); + $firstAssociatedProduct = new Product(); $firstAssociatedProduct->setCode('12345'); $secondAssociatedProduct = new Product(); @@ -50,6 +54,66 @@ public function testProcessingBoughtTogetherProductsIntoAssociation(): void $productAssociation->addAssociatedProduct($firstAssociatedProduct)->shouldBeCalled(); $productAssociation->addAssociatedProduct($secondAssociatedProduct)->shouldBeCalled(); + $entityManager->persist($productAssociation)->shouldBeCalled(); + $entityManager->flush()->shouldBeCalledOnce(); + $processor(new SynchronizationEnded(Uuid::v4(), new \DateTimeImmutable(), 10, ['10137'])); } + + public function testItFlushesProgressInBatches(): void + { + $entityManager = $this->prophesize(EntityManagerInterface::class); + $productRepository = $this->prophesize(ProductRepositoryInterface::class); + $boughtTogetherProductsAssociationProvider = $this->prophesize(BoughtTogetherProductsAssociationProviderInterface::class); + $numberOfSynchronizableProductsProvider = $this->prophesize(SynchronizableProductsNumberProviderInterface::class); + + $processor = new BoughtTogetherProductsAssociationProcessor( + $entityManager->reveal(), + $productRepository->reveal(), + $boughtTogetherProductsAssociationProvider->reveal(), + $numberOfSynchronizableProductsProvider->reveal(), + 1, + ); + + $firstProduct = $this->prophesize(Product::class); + $secondProduct = $this->prophesize(Product::class); + $firstProductAssociation = $this->prophesize(ProductAssociation::class); + $secondProductAssociation = $this->prophesize(ProductAssociation::class); + + $boughtTogetherProductsAssociationProvider->getForProduct($firstProduct->reveal())->willReturn($firstProductAssociation); + $firstProduct->getBoughtTogetherProducts()->willReturn(['12345' => 2, '500' => 1]); + + $boughtTogetherProductsAssociationProvider->getForProduct($secondProduct->reveal())->willReturn($secondProductAssociation); + $secondProduct->getBoughtTogetherProducts()->willReturn(['10' => 2, '20' => 1]); + + $numberOfSynchronizableProductsProvider->getNumberOfProductsToSynchronise($firstProduct->reveal())->willReturn(10); + $numberOfSynchronizableProductsProvider->getNumberOfProductsToSynchronise($secondProduct->reveal())->willReturn(10); + + $firstAssociatedProduct = new Product(); + $firstAssociatedProduct->setCode('12345'); + $secondAssociatedProduct = new Product(); + $secondAssociatedProduct->setCode('500'); + $thirdAssociatedProduct = new Product(); + $thirdAssociatedProduct->setCode('10'); + $fourthAssociatedProduct = new Product(); + $fourthAssociatedProduct->setCode('20'); + + $productRepository->findBy(['code' => ['10137', '12345']])->willReturn([$firstProduct, $secondProduct]); + $productRepository->findBy(['code' => ['12345', '500']])->willReturn([$firstAssociatedProduct, $secondAssociatedProduct]); + $productRepository->findBy(['code' => ['10', '20']])->willReturn([$thirdAssociatedProduct, $fourthAssociatedProduct]); + + $firstProductAssociation->clearAssociatedProducts()->shouldBeCalled(); + $firstProductAssociation->addAssociatedProduct($firstAssociatedProduct)->shouldBeCalled(); + $firstProductAssociation->addAssociatedProduct($secondAssociatedProduct)->shouldBeCalled(); + + $secondProductAssociation->clearAssociatedProducts()->shouldBeCalled(); + $secondProductAssociation->addAssociatedProduct($thirdAssociatedProduct)->shouldBeCalled(); + $secondProductAssociation->addAssociatedProduct($fourthAssociatedProduct)->shouldBeCalled(); + + $entityManager->persist($firstProductAssociation)->shouldBeCalled(); + $entityManager->persist($secondProductAssociation)->shouldBeCalled(); + $entityManager->flush()->shouldBeCalledTimes(3); + + $processor(new SynchronizationEnded(Uuid::v4(), new \DateTimeImmutable(), 10, ['10137', '12345'])); + } } diff --git a/tests/Unit/Provider/SynchronizableProductsNumberProviderTest.php b/tests/Unit/Provider/SynchronizableProductsNumberProviderTest.php new file mode 100644 index 0000000..3dfc1f7 --- /dev/null +++ b/tests/Unit/Provider/SynchronizableProductsNumberProviderTest.php @@ -0,0 +1,67 @@ +setNumberOfSynchronisedProducts(5); + $product->addChannel($channel); + + $provider = new SynchronizableProductsNumberProvider(); + $this->assertSame(5, $provider->getNumberOfProductsToSynchronise($product)); + } + + public function testItProvidesTheHighestOfConfiguredNumbers(): void + { + $product = new Product(); + $firstChannel = new class extends Channel implements BoughtTogetherConfigurableChannelInterface { + use BoughtTogetherConfigurableChannelTrait; + }; + $firstChannel->setNumberOfSynchronisedProducts(5); + $product->addChannel($firstChannel); + $secondChannel = new class extends Channel implements BoughtTogetherConfigurableChannelInterface { + use BoughtTogetherConfigurableChannelTrait; + }; + $secondChannel->setNumberOfSynchronisedProducts(10); + $product->addChannel($secondChannel); + $thirdChannel = new class extends Channel implements BoughtTogetherConfigurableChannelInterface { + use BoughtTogetherConfigurableChannelTrait; + }; + $thirdChannel->setNumberOfSynchronisedProducts(7); + $product->addChannel($thirdChannel); + + $provider = new SynchronizableProductsNumberProvider(); + $this->assertSame(10, $provider->getNumberOfProductsToSynchronise($product)); + } + + public function testItProvidesTheDefaultNumberOfProductsIfNoChannelConfigured(): void + { + $product = new Product(); + + $provider = new SynchronizableProductsNumberProvider(); + $this->assertSame(10, $provider->getNumberOfProductsToSynchronise($product)); + } + + public function testItProvidesTheCustomDefaultNumberOfProductsIfNoChannelConfigured(): void + { + $product = new Product(); + + $provider = new SynchronizableProductsNumberProvider(15); + $this->assertSame(15, $provider->getNumberOfProductsToSynchronise($product)); + } +} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 843d85f..df8ed9e 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -1,4 +1,7 @@ commerce_weavers_sylius_also_bought: + form: + channel: + number_of_synchronised_products: 'Number of synchronised products' missing_configuration_error_message: title: 'SyliusAlsoBoughtPlugin: manual action required' error_explanation: 'SyliusAlsoBoughtPlugin is installed, but the necessary product association type with code "bought_together" does not exist.'