mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 09:02:12 +00:00
Merge pull request #6539 from lchrusciel/product-filter-type
[Promotion] UI for product filter
This commit is contained in:
commit
741228b5a7
9 changed files with 322 additions and 1 deletions
|
|
@ -42,6 +42,18 @@ Feature: Adding promotion with filter
|
|||
Then I should be notified that it has been successfully created
|
||||
And the "$10 discount for all T-Shirts!" promotion should appear in the registry
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Adding a promotion with fixed discount for PHP T-Shirt
|
||||
Given the store has a product "PHP T-Shirt" priced at "$100.00"
|
||||
And I want to create a new promotion
|
||||
When I specify its code as "10_for_php_t_shirt"
|
||||
And I name it "$10 discount for PHP T-Shirts!"
|
||||
And I add the "Item fixed discount" action configured with amount of "$10"
|
||||
And I specify that this action should be applied to the "PHP T-Shirt" product
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "$10 discount for PHP T-Shirts!" promotion should appear in the registry
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Adding a promotion with item percentage discount only for products over 10
|
||||
Given I want to create a new promotion
|
||||
|
|
@ -75,3 +87,15 @@ Feature: Adding promotion with filter
|
|||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "$10 discount for all T-Shirts!" promotion should appear in the registry
|
||||
|
||||
@ui @javascript
|
||||
Scenario: Adding a promotion with 10% percentage discount for PHP T-Shirt
|
||||
Given the store has a product "PHP T-Shirt" priced at "$100.00"
|
||||
And I want to create a new promotion
|
||||
When I specify its code as "10_for_php_t_shirt"
|
||||
And I name it "10% discount for PHP T-Shirts!"
|
||||
And I add the "Item percentage discount" action configured with a percentage value of 10%
|
||||
And I specify that this action should be applied to the "PHP T-Shirt" product
|
||||
And I add it
|
||||
Then I should be notified that it has been successfully created
|
||||
And the "10% discount for PHP T-Shirts!" promotion should appear in the registry
|
||||
|
|
|
|||
|
|
@ -554,6 +554,14 @@ final class ManagingPromotionsContext implements Context
|
|||
$this->createPage->selectRuleOption('Product', $productName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify that this action should be applied to the :productName product
|
||||
*/
|
||||
public function iSpecifyThatThisActionShouldBeAppliedToTheProduct($productName)
|
||||
{
|
||||
$this->createPage->selectFilterOption('Products', $productName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $element
|
||||
* @param string $expectedMessage
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\DataTransformer;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Resource\Exception\UnexpectedTypeException;
|
||||
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
final class ProductsToCodesTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* @var ProductRepositoryInterface
|
||||
*/
|
||||
private $productRepository;
|
||||
|
||||
/**
|
||||
* @param ProductRepositoryInterface $productRepository
|
||||
*/
|
||||
public function __construct(ProductRepositoryInterface $productRepository)
|
||||
{
|
||||
$this->productRepository = $productRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value)
|
||||
{
|
||||
if (!is_array($value) && !is_null($value)) {
|
||||
throw new UnexpectedTypeException($value, 'array');
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
return new ArrayCollection($this->productRepository->findBy(['code' => $value]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reverseTransform($products)
|
||||
{
|
||||
if (!$products instanceof Collection) {
|
||||
throw new UnexpectedTypeException($products, Collection::class);
|
||||
}
|
||||
|
||||
if (null === $products) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$productCodes = [];
|
||||
|
||||
/** @var ProductInterface $product */
|
||||
foreach ($products as $product) {
|
||||
$productCodes[] = $product->getCode();
|
||||
}
|
||||
|
||||
return $productCodes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Filter;
|
||||
|
||||
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
class ProductFilterConfigurationType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var ProductRepositoryInterface
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* @var DataTransformerInterface
|
||||
*/
|
||||
private $productsToCodesTransformer;
|
||||
|
||||
/**
|
||||
* @param ProductRepositoryInterface $productRepository
|
||||
* @param DataTransformerInterface $productsToCodesTransformer
|
||||
*/
|
||||
public function __construct(
|
||||
ProductRepositoryInterface $productRepository,
|
||||
DataTransformerInterface $productsToCodesTransformer
|
||||
) {
|
||||
$this->productRepository = $productRepository;
|
||||
$this->productsToCodesTransformer = $productsToCodesTransformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('products', 'sylius_product_choice', [
|
||||
'label' => 'sylius.form.promotion_rule.product.products',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
|
||||
$builder->get('products')->addModelTransformer($this->productsToCodesTransformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'sylius_promotion_action_filter_product_configuration';
|
||||
}
|
||||
}
|
||||
|
|
@ -27,5 +27,6 @@ class PromotionFiltersType extends BasePromotionFiltersType
|
|||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->add('taxons_filter', 'sylius_promotion_action_filter_taxon_configuration', ['required' => false]);
|
||||
$builder->add('products_filter', 'sylius_promotion_action_filter_product_configuration', ['required' => false]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,11 @@
|
|||
<argument type="service" id="sylius.form.type.data_transformer.taxons_to_codes" />
|
||||
<tag name="form.type" alias="sylius_promotion_action_filter_taxon_configuration" />
|
||||
</service>
|
||||
<service id="sylius.form.type.promotion_action.filter.product" class="Sylius\Bundle\CoreBundle\Form\Type\Promotion\Filter\ProductFilterConfigurationType">
|
||||
<argument type="service" id="sylius.repository.product" />
|
||||
<argument type="service" id="sylius.form.type.data_transformer.products_to_codes" />
|
||||
<tag name="form.type" alias="sylius_promotion_action_filter_product_configuration" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.form.type.price_calculator.channel_based" class="Sylius\Bundle\CoreBundle\Form\Type\Pricing\ChannelBasedConfigurationType">
|
||||
<argument type="service" id="sylius.repository.channel" />
|
||||
|
|
@ -130,6 +135,9 @@
|
|||
<service id="sylius.form.type.data_transformer.taxons_to_codes" class="Sylius\Bundle\CoreBundle\Form\DataTransformer\TaxonsToCodesTransformer">
|
||||
<argument type="service" id="sylius.repository.taxon" />
|
||||
</service>
|
||||
<service id="sylius.form.type.data_transformer.products_to_codes" class="Sylius\Bundle\CoreBundle\Form\DataTransformer\ProductsToCodesTransformer">
|
||||
<argument type="service" id="sylius.repository.product" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.form.type.customer_guest" class="Sylius\Bundle\CoreBundle\Form\Type\Customer\CustomerGuestType">
|
||||
<argument>%sylius.model.customer.class%</argument>
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ sylius:
|
|||
count: Count
|
||||
taxon:
|
||||
taxons: Taxons
|
||||
product:
|
||||
products: Products
|
||||
total_of_items_from_taxon:
|
||||
taxon: Taxon
|
||||
amount: Amount
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
namespace spec\Sylius\Bundle\CoreBundle\Form\DataTransformer;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\CoreBundle\Form\DataTransformer\ProductsToCodesTransformer;
|
||||
use Sylius\Component\Core\Model\ProductInterface;
|
||||
use Sylius\Component\Resource\Exception\UnexpectedTypeException;
|
||||
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
|
||||
/**
|
||||
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
|
||||
*/
|
||||
final class ProductsToCodesTransformerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(ProductRepositoryInterface $productRepository)
|
||||
{
|
||||
$this->beConstructedWith($productRepository);
|
||||
}
|
||||
|
||||
function it_is_initializable()
|
||||
{
|
||||
$this->shouldHaveType(ProductsToCodesTransformer::class);
|
||||
}
|
||||
|
||||
function it_implements_data_transformer_interface()
|
||||
{
|
||||
$this->shouldImplement(DataTransformerInterface::class);
|
||||
}
|
||||
|
||||
function it_transforms_array_of_products_codes_to_products_collection(
|
||||
ProductRepositoryInterface $productRepository,
|
||||
ProductInterface $bow,
|
||||
ProductInterface $sword
|
||||
) {
|
||||
$productRepository->findBy(['code' => ['bow', 'sword']])->willReturn([$bow, $sword]);
|
||||
|
||||
$products = new ArrayCollection([$bow->getWrappedObject(), $sword->getWrappedObject()]);
|
||||
|
||||
$this->transform(['bow', 'sword'])->shouldBeCollection($products);
|
||||
}
|
||||
|
||||
function it_transforms_only_existing_products(
|
||||
ProductRepositoryInterface $productRepository,
|
||||
ProductInterface $bow
|
||||
) {
|
||||
$productRepository->findBy(['code' => ['bow', 'sword']])->willReturn([$bow]);
|
||||
|
||||
$products = new ArrayCollection([$bow->getWrappedObject()]);
|
||||
|
||||
$this->transform(['bow', 'sword'])->shouldBeCollection($products);
|
||||
}
|
||||
|
||||
function it_transforms_empty_array_into_empty_collection()
|
||||
{
|
||||
$this->transform([])->shouldBeCollection(new ArrayCollection([]));
|
||||
}
|
||||
|
||||
function it_throws_exception_if_value_to_transform_is_not_array()
|
||||
{
|
||||
$this
|
||||
->shouldThrow(new UnexpectedTypeException('badObject', 'array'))
|
||||
->during('transform', ['badObject'])
|
||||
;
|
||||
}
|
||||
|
||||
function it_reverse_transforms_into_array_of_products_codes(
|
||||
ProductInterface $axes,
|
||||
ProductInterface $shields
|
||||
) {
|
||||
$axes->getCode()->willReturn('axes');
|
||||
$shields->getCode()->willReturn('shields');
|
||||
|
||||
$this
|
||||
->reverseTransform(new ArrayCollection([$axes->getWrappedObject(), $shields->getWrappedObject()]))
|
||||
->shouldReturn(['axes', 'shields'])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_exception_if_reverse_transformed_object_is_not_collection()
|
||||
{
|
||||
$this
|
||||
->shouldThrow(new UnexpectedTypeException('badObject', Collection::class))
|
||||
->during('reverseTransform', ['badObject'])
|
||||
;
|
||||
}
|
||||
|
||||
function it_returns_empty_array_if_passed_collection_is_empty()
|
||||
{
|
||||
$this->reverseTransform(new ArrayCollection())->shouldReturn([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMatchers()
|
||||
{
|
||||
return [
|
||||
'beCollection' => function ($subject, $key) {
|
||||
if (!$subject instanceof Collection || !$key instanceof Collection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($subject->count() !== $key->count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($subject as $subjectElement) {
|
||||
if (!$key->contains($subjectElement)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ final class TaxonFilter implements FilterInterface
|
|||
*/
|
||||
public function filter(array $items, array $configuration)
|
||||
{
|
||||
if (!isset($configuration['filters']['taxons_filter'])) {
|
||||
if (!isset($configuration['filters']['taxons_filter']) || empty($configuration['filters']['taxons_filter']['taxons'])) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue