Include children taxons while executing InForTaxonsScopeVariantChecker

This commit is contained in:
Jakub Tobiasz 2023-06-21 12:41:30 +02:00
parent f9633e9745
commit 8e38c77061
No known key found for this signature in database
GPG key ID: 6434250CB3525233
8 changed files with 186 additions and 4 deletions

View file

@ -7,12 +7,13 @@ Feature: Applying catalog promotions for taxon
Background:
Given the store operates on a single channel in "United States"
And the store classifies its products as "Clothes" and "Dishes"
And the "Clothes" taxon has child taxon "Jeans"
And the "Jeans" taxon has child taxon "Programmer's jeans"
And the store has a "T-Shirt" configurable product
And this product belongs to "Clothes"
And this product has "PHP T-Shirt" variant priced at "$20.00"
And the store has a "Pants" configurable product
And this product belongs to "Clothes"
And this product has "Aladdin Pants" variant priced at "$100.00"
And the store has a product "Blue Jeans" priced at "$100.00"
And this product belongs to "Jeans"
And the store has a "Mug" configurable product
And this product belongs to "Dishes"
And this product has "PHP Mug" variant priced at "$5.00"
@ -24,6 +25,12 @@ Feature: Applying catalog promotions for taxon
Then I should see the product price "$14.00"
And I should see the product original price "$20.00"
@api @ui
Scenario: Applying simple catalog promotions on a product belongs to child taxon
When I view product "Blue Jeans"
Then I should see the product price "$70.00"
And I should see the product original price "$100.00"
@api @ui
Scenario: Applying multiple catalog promotions
Given there is a catalog promotion "Summer sale" that reduces price by "10%" and applies on "Clothes" taxon

View file

@ -13,16 +13,24 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\CatalogPromotion\Checker;
use Sylius\Bundle\TaxonomyBundle\Repository\TaxonTreeRepositoryInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Promotion\Model\CatalogPromotionScopeInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
use Webmozart\Assert\Assert;
final class InForTaxonsScopeVariantChecker implements VariantInScopeCheckerInterface
{
public const TYPE = 'for_taxons';
public function __construct (
private TaxonRepositoryInterface $taxonRepository,
private TaxonTreeRepositoryInterface $taxonTreeRepository,
) {
}
public function inScope(CatalogPromotionScopeInterface $scope, ProductVariantInterface $productVariant): bool
{
$configuration = $scope->getConfiguration();
@ -31,8 +39,29 @@ final class InForTaxonsScopeVariantChecker implements VariantInScopeCheckerInter
/** @var ProductInterface $product */
$product = $productVariant->getProduct();
$promotionTaxons = $configuration['taxons'];
foreach ($scope->getConfiguration()['taxons'] as $taxonCode) {
/** @var TaxonInterface $taxon */
$taxon = $this->taxonRepository->findOneBy(['code' => $taxonCode]);
Assert::notNull($taxon, sprintf('Taxon with code "%s" does not exist', $taxonCode));
$promotionTaxons = array_merge($promotionTaxons, $this->getTaxonChildrenCodes($taxon));
}
return $product->getTaxons()->exists(
fn ($key, TaxonInterface $taxon): bool => \in_array($taxon->getCode(), $scope->getConfiguration()['taxons'], true),
fn ($key, TaxonInterface $taxon): bool => \in_array($taxon->getCode(), $promotionTaxons, true),
);
}
/** @return array<string> */
private function getTaxonChildrenCodes(TaxonInterface $taxon): array
{
$childrenCodes = [];
foreach ($this->taxonTreeRepository->children($taxon) as $taxonChild) {
$childrenCodes[] = $taxonChild->getCode();
}
return $childrenCodes;
}
}

View file

@ -31,6 +31,8 @@
</service>
<service id="Sylius\Bundle\CoreBundle\CatalogPromotion\Checker\InForTaxonsScopeVariantChecker">
<argument type="service" id="sylius.repository.taxon" />
<argument type="service" id="sylius.repository.tree.taxon" />
<tag name="sylius.catalog_promotion.variant_checker" type="for_taxons" />
</service>

View file

@ -16,26 +16,46 @@ namespace spec\Sylius\Bundle\CoreBundle\CatalogPromotion\Checker;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\CoreBundle\CatalogPromotion\Checker\VariantInScopeCheckerInterface;
use Sylius\Bundle\TaxonomyBundle\Repository\TaxonTreeRepositoryInterface;
use Sylius\Component\Core\Model\CatalogPromotionScopeInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
final class InForTaxonsScopeVariantCheckerSpec extends ObjectBehavior
{
function let(TaxonRepositoryInterface $taxonRepository, TaxonTreeRepositoryInterface $taxonTreeRepository): void
{
$this->beConstructedWith($taxonRepository, $taxonTreeRepository);
}
function it_implements_catalog_promotion_price_calculator_interface(): void
{
$this->shouldImplement(VariantInScopeCheckerInterface::class);
}
public function it_returns_true_if_variant_taxon_is_in_scope_configuration(
TaxonRepositoryInterface $taxonRepository,
TaxonTreeRepositoryInterface $taxonTreeRepository,
CatalogPromotionScopeInterface $scope,
ProductVariantInterface $variant,
ProductInterface $product,
TaxonInterface $firstTaxon,
TaxonInterface $secondTaxon,
TaxonInterface $thirdTaxon,
TaxonInterface $fourthTaxon,
): void {
$taxonRepository->findOneBy(['code' => 'FIRST_TAXON'])->willReturn($firstTaxon);
$taxonRepository->findOneBy(['code' => 'SECOND_TAXON'])->willReturn($secondTaxon);
$taxonRepository->findOneBy(['code' => 'THIRD_TAXON'])->willReturn($thirdTaxon);
$taxonRepository->findOneBy(['code' => 'FOURTH_TAXON'])->willReturn($fourthTaxon);
$taxonTreeRepository->children($firstTaxon)->willReturn([]);
$taxonTreeRepository->children($secondTaxon)->willReturn([]);
$taxonTreeRepository->children($thirdTaxon)->willReturn([]);
$taxonTreeRepository->children($fourthTaxon)->willReturn([]);
$scope->getConfiguration()->willReturn(['taxons' => ['FIRST_TAXON', 'SECOND_TAXON']]);
$variant->getProduct()->willReturn($product);
@ -44,11 +64,38 @@ final class InForTaxonsScopeVariantCheckerSpec extends ObjectBehavior
$firstTaxon->getCode()->willReturn('FIRST_TAXON');
$secondTaxon->getCode()->willReturn('SECOND_TAXON');
$thirdTaxon->getCode()->willReturn('THIRD_TAXON');
$fourthTaxon->getCode()->willReturn('FOURTH_TAXON');
$this->inScope($scope, $variant)->shouldReturn(true);
}
public function it_returns_true_if_variant_taxon_is_a_child_of_taxon_in_the_scope_configuration(
TaxonRepositoryInterface $taxonRepository,
TaxonTreeRepositoryInterface $taxonTreeRepository,
CatalogPromotionScopeInterface $scope,
ProductVariantInterface $variant,
ProductInterface $product,
TaxonInterface $firstTaxon,
TaxonInterface $secondTaxon,
): void {
$taxonRepository->findOneBy(['code' => 'FIRST_TAXON'])->willReturn($firstTaxon);
$taxonTreeRepository->children($firstTaxon)->willReturn([$secondTaxon]);
$scope->getConfiguration()->willReturn(['taxons' => ['FIRST_TAXON']]);
$variant->getProduct()->willReturn($product);
$product->getTaxons()->willReturn(new ArrayCollection([$secondTaxon->getWrappedObject()]));
$firstTaxon->getCode()->willReturn('FIRST_TAXON');
$secondTaxon->getCode()->willReturn('SECOND_TAXON');
$this->inScope($scope, $variant)->shouldReturn(true);
}
public function it_returns_false_if_variant_taxon_is_not_in_scope_configuration(
TaxonRepositoryInterface $taxonRepository,
TaxonTreeRepositoryInterface $taxonTreeRepository,
CatalogPromotionScopeInterface $scope,
ProductVariantInterface $variant,
ProductInterface $product,
@ -56,6 +103,12 @@ final class InForTaxonsScopeVariantCheckerSpec extends ObjectBehavior
TaxonInterface $secondTaxon,
TaxonInterface $thirdTaxon,
): void {
$taxonRepository->findOneBy(['code' => 'FIRST_TAXON'])->willReturn($firstTaxon);
$taxonRepository->findOneBy(['code' => 'SECOND_TAXON'])->willReturn($secondTaxon);
$taxonTreeRepository->children($firstTaxon)->willReturn([]);
$taxonTreeRepository->children($secondTaxon)->willReturn([]);
$scope->getConfiguration()->willReturn(['taxons' => ['FIRST_TAXON', 'SECOND_TAXON']]);
$variant->getProduct()->willReturn($product);

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\TaxonomyBundle\Repository;
use Gedmo\Tree\Entity\Repository\NestedTreeRepository;
class TaxonTreeRepository implements TaxonTreeRepositoryInterface
{
public function __construct (
private NestedTreeRepository $nestedTreeRepository,
) {
}
public function children(?object $node = null, bool $direct = false, array|string|null $sortByField = null, array|string $direction = 'ASC', bool $includeNode = false,): array|null
{
return $this->nestedTreeRepository->children($node, $direct, $sortByField, $direction, $includeNode);
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\TaxonomyBundle\Repository;
interface TaxonTreeRepositoryInterface
{
/**
* @param string|string[]|null $sortByField
* @param string|string[] $direction
*
* @return array<object>|null
*/
public function children(
?object $node = null,
bool $direct = false,
string|array|null $sortByField = null,
string|array $direction = 'ASC',
bool $includeNode = false,
): array|null;
}

View file

@ -14,6 +14,7 @@
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<imports>
<import resource="services/form.xml" />
<import resource="services/tree_repository.xml" />
</imports>
<services>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Sylius\Bundle\TaxonomyBundle\Repository\TaxonTreeRepositoryInterface" class="Sylius\Bundle\TaxonomyBundle\Repository\TaxonTreeRepository" decorates="sylius.repository.tree.taxon">
<argument type="service" id="Sylius\Bundle\TaxonomyBundle\Repository\TaxonTreeRepositoryInterface.inner" />
</service>
<service id="sylius.repository.tree.taxon" class="Gedmo\Tree\Entity\Repository\NestedTreeRepository">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service">
<service class="Doctrine\ORM\Mapping\ClassMetadata">
<factory service="doctrine.orm.entity_manager" method="getClassMetadata" />
<argument>%sylius.model.taxon.class%</argument>
</service>
</argument>
</service>
</services>
</container>