diff --git a/features/promotion/managing_promotions/adding_promotion_with_filter.feature b/features/promotion/managing_promotions/adding_promotion_with_filter.feature
index 8a490cd5e4..199f3e18d7 100644
--- a/features/promotion/managing_promotions/adding_promotion_with_filter.feature
+++ b/features/promotion/managing_promotions/adding_promotion_with_filter.feature
@@ -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
diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingPromotionsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingPromotionsContext.php
index 0f9445cfa5..7585a9a96e 100644
--- a/src/Sylius/Behat/Context/Ui/Admin/ManagingPromotionsContext.php
+++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingPromotionsContext.php
@@ -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
diff --git a/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductsToCodesTransformer.php b/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductsToCodesTransformer.php
new file mode 100644
index 0000000000..15b7387503
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductsToCodesTransformer.php
@@ -0,0 +1,77 @@
+
+ */
+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;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/Filter/ProductFilterConfigurationType.php b/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/Filter/ProductFilterConfigurationType.php
new file mode 100644
index 0000000000..46491c170d
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/Filter/ProductFilterConfigurationType.php
@@ -0,0 +1,71 @@
+
+ */
+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';
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/PromotionFiltersType.php b/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/PromotionFiltersType.php
index 4ce4fc8f35..0fb03970bc 100644
--- a/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/PromotionFiltersType.php
+++ b/src/Sylius/Bundle/CoreBundle/Form/Type/Promotion/PromotionFiltersType.php
@@ -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]);
}
}
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml
index 757ee72b71..50201c5cda 100644
--- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml
+++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml
@@ -113,6 +113,11 @@
+
+
+
+
+
@@ -130,6 +135,9 @@
+
+
+
%sylius.model.customer.class%
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml
index d4e5a53925..a73b4faae7 100644
--- a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml
@@ -125,6 +125,8 @@ sylius:
count: Count
taxon:
taxons: Taxons
+ product:
+ products: Products
total_of_items_from_taxon:
taxon: Taxon
amount: Amount
diff --git a/src/Sylius/Bundle/CoreBundle/spec/Form/DataTransformer/ProductsToCodesTransformerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/Form/DataTransformer/ProductsToCodesTransformerSpec.php
new file mode 100644
index 0000000000..b5013b8ea5
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/Form/DataTransformer/ProductsToCodesTransformerSpec.php
@@ -0,0 +1,130 @@
+
+ */
+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;
+ },
+ ];
+ }
+}
diff --git a/src/Sylius/Component/Core/Promotion/Filter/TaxonFilter.php b/src/Sylius/Component/Core/Promotion/Filter/TaxonFilter.php
index 8d68460178..68f21e4ebe 100644
--- a/src/Sylius/Component/Core/Promotion/Filter/TaxonFilter.php
+++ b/src/Sylius/Component/Core/Promotion/Filter/TaxonFilter.php
@@ -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;
}