diff --git a/features/promotion/receiving_discount/receiving_fixed_discount_on_items_on_price_range.feature b/features/promotion/receiving_discount/receiving_fixed_discount_on_items_on_price_range.feature index e523e9a056..9b8b50a7d5 100644 --- a/features/promotion/receiving_discount/receiving_fixed_discount_on_items_on_price_range.feature +++ b/features/promotion/receiving_discount/receiving_fixed_discount_on_items_on_price_range.feature @@ -8,6 +8,7 @@ Feature: Receiving fixed discount on products from specific price range Given the store operates on a single channel in "United States" And the store has a product "PHP T-Shirt" priced at "$100.00" And the store has a product "PHP Mug" priced at "$20.00" + And the store has a product "PHP Sticker" priced at "$5.00" And there is a promotion "Christmas promotion" @ui @api @@ -24,6 +25,20 @@ Feature: Receiving fixed discount on products from specific price range Then its price should be decreased by "$10.00" And my cart total should be "$90.00" + @ui @api + Scenario: Receiving fixed discount on a single item fulfilling maximum price criteria + Given the promotion gives "$10.00" off on every product with maximum price at "$50.00" + When I add product "PHP Mug" to the cart + Then its price should be decreased by "$10.00" + And my cart total should be "$10.00" + + @ui @api + Scenario: Receiving fixed discount on a single item with price equal to filter maximum criteria + Given the promotion gives "$10.00" off on every product with minimum price at "$20.00" + When I add product "PHP Mug" to the cart + Then its price should be decreased by "$10.00" + And my cart total should be "$10.00" + @ui @api Scenario: Receiving fixed discount on a single item fulfilling range price criteria Given the promotion gives "$10.00" off on every product priced between "$15.00" and "$50.00" @@ -80,8 +95,12 @@ Feature: Receiving fixed discount on products from specific price range Given the promotion gives "$10.00" off on every product with minimum price at "$80.00" And there is a promotion "Mugs promotion" And it gives "$5.00" off on every product priced between "$10.00" and "$50.00" + And there is a promotion "Stickers promotion" + And it gives "$1.00" off on every product with maximum price at "$10.00" When I add product "PHP T-Shirt" to the cart And I add product "PHP Mug" to the cart + And I add product "PHP Sticker" to the cart Then product "PHP T-Shirt" price should be decreased by "$10.00" And product "PHP Mug" price should be decreased by "$5.00" - And my cart total should be "$105.00" + And product "PHP Sticker" price should be decreased by "$1.00" + And my cart total should be "$109.00" diff --git a/features/promotion/receiving_discount/receiving_percentage_discount_on_items_on_price_range.feature b/features/promotion/receiving_discount/receiving_percentage_discount_on_items_on_price_range.feature index 88d04af91a..04f84a7df6 100644 --- a/features/promotion/receiving_discount/receiving_percentage_discount_on_items_on_price_range.feature +++ b/features/promotion/receiving_discount/receiving_percentage_discount_on_items_on_price_range.feature @@ -8,6 +8,7 @@ Feature: Receiving percentage discount on products from specific price range Given the store operates on a single channel in "United States" And the store has a product "PHP T-Shirt" priced at "$100.00" And the store has a product "PHP Mug" priced at "$20.00" + And the store has a product "PHP Sticker" priced at "$5.00" And there is a promotion "Christmas sale" @ui @api @@ -24,6 +25,20 @@ Feature: Receiving percentage discount on products from specific price range Then its price should be decreased by "$10.00" And my cart total should be "$90.00" + @ui @api + Scenario: Receiving percentage discount on a single item fulfilling maximum price criteria + Given this promotion gives "10%" off on every product with maximum price at "$50.00" + When I add product "PHP Mug" to the cart + Then its price should be decreased by "$2.00" + And my cart total should be "$18.00" + + @ui @api + Scenario: Receiving percentage discount on a single item with price equal to filter maximum criteria + Given this promotion gives "10%" off on every product with maximum price at "$20.00" + When I add product "PHP Mug" to the cart + Then its price should be decreased by "$2.00" + And my cart total should be "$18.00" + @ui @api Scenario: Receiving percentage discount on a single item fulfilling range price criteria Given this promotion gives "50%" off on every product priced between "$15.00" and "$50.00" @@ -66,8 +81,12 @@ Feature: Receiving percentage discount on products from specific price range Given this promotion gives "10%" off on every product with minimum price at "$80.00" And there is a promotion "Mugs promotion" And it gives "90%" off on every product priced between "$10.00" and "$90.00" + And there is a promotion "Stickers promotion" + And it gives "20%" off on every product with maximum price at "$10.00" When I add product "PHP T-Shirt" to the cart And I add product "PHP Mug" to the cart + And I add product "PHP Sticker" to the cart Then product "PHP T-Shirt" price should be decreased by "$10.00" And product "PHP Mug" price should be decreased by "$18.00" - And my cart total should be "$92.00" + And product "PHP Sticker" price should be decreased by "$1.00" + And my cart total should be "$96.00" diff --git a/src/Sylius/Behat/Context/Setup/PromotionContext.php b/src/Sylius/Behat/Context/Setup/PromotionContext.php index 2edc7b6a06..c401b3128e 100644 --- a/src/Sylius/Behat/Context/Setup/PromotionContext.php +++ b/src/Sylius/Behat/Context/Setup/PromotionContext.php @@ -512,6 +512,21 @@ final class PromotionContext implements Context $this->createUnitFixedPromotion($promotion, $discount, $this->getPriceRangeFilterConfiguration($amount)); } + /** + * @Given /^([^"]+) gives ("(?:€|£|\$)[^"]+") off on every product with maximum price at ("(?:€|£|\$)[^"]+")$/ + */ + public function thisPromotionGivesOffOnEveryProductWithMaximumPriceAt( + PromotionInterface $promotion, + int $discount, + int $amount, + ): void { + $this->createUnitFixedPromotion( + $promotion, + $discount, + $this->getPriceRangeFilterConfiguration(maxAmount: $amount), + ); + } + /** * @Given /^([^"]+) gives ("(?:€|£|\$)[^"]+") off on every product priced between ("(?:€|£|\$)[^"]+") and ("(?:€|£|\$)[^"]+")$/ */ @@ -539,6 +554,21 @@ final class PromotionContext implements Context $this->createUnitPercentagePromotion($promotion, $discount, $this->getPriceRangeFilterConfiguration($amount)); } + /** + * @Given /^([^"]+) gives ("[^"]+%") off on every product with maximum price at ("(?:€|£|\$)[^"]+")$/ + */ + public function thisPromotionPercentageGivesOffOnEveryProductWithMaximumPriceAt( + PromotionInterface $promotion, + float $discount, + int $amount, + ): void { + $this->createUnitPercentagePromotion( + $promotion, + $discount, + $this->getPriceRangeFilterConfiguration(maxAmount: $amount), + ); + } + /** * @Given /^([^"]+) gives ("[^"]+%") off on every product priced between ("(?:€|£|\$)[^"]+") and ("(?:€|£|\$)[^"]+")$/ */ @@ -893,9 +923,13 @@ final class PromotionContext implements Context return ['filters' => ['products_filter' => ['products' => $productCodes]]]; } - private function getPriceRangeFilterConfiguration(int $minAmount, int $maxAmount = null): array + private function getPriceRangeFilterConfiguration(?int $minAmount = null, ?int $maxAmount = null): array { - $configuration = ['filters' => ['price_range_filter' => ['min' => $minAmount]]]; + $configuration = []; + + if (null !== $minAmount) { + $configuration['filters']['price_range_filter']['min'] = $minAmount; + } if (null !== $maxAmount) { $configuration['filters']['price_range_filter']['max'] = $maxAmount; } diff --git a/src/Sylius/Component/Core/Promotion/Filter/PriceRangeFilter.php b/src/Sylius/Component/Core/Promotion/Filter/PriceRangeFilter.php index 2c77c1360a..fc2a598299 100644 --- a/src/Sylius/Component/Core/Promotion/Filter/PriceRangeFilter.php +++ b/src/Sylius/Component/Core/Promotion/Filter/PriceRangeFilter.php @@ -50,11 +50,17 @@ final class PriceRangeFilter implements FilterInterface return $priceRange['min'] <= $price && $priceRange['max'] >= $price; } - return $priceRange['min'] <= $price; + return + (!isset($priceRange['min']) && $priceRange['max'] >= $price) || + (!isset($priceRange['max']) && $priceRange['min'] <= $price) + ; } private function isConfigured(array $configuration): bool { - return isset($configuration['filters']['price_range_filter']['min']); + return + isset($configuration['filters']['price_range_filter']['min']) || + isset($configuration['filters']['price_range_filter']['max']) + ; } } diff --git a/src/Sylius/Component/Core/spec/Promotion/Filter/PriceRangeFilterSpec.php b/src/Sylius/Component/Core/spec/Promotion/Filter/PriceRangeFilterSpec.php index 79d5be89e8..8e93280e3e 100644 --- a/src/Sylius/Component/Core/spec/Promotion/Filter/PriceRangeFilterSpec.php +++ b/src/Sylius/Component/Core/spec/Promotion/Filter/PriceRangeFilterSpec.php @@ -155,16 +155,62 @@ final class PriceRangeFilterSpec extends ObjectBehavior ; } + function it_filters_items_which_has_product_with_price_that_is_bigger_than_configured_maximum_criteria( + ChannelInterface $channel, + OrderItemInterface $item1, + OrderItemInterface $item2, + OrderItemInterface $item3, + ProductVariantInterface $item1Variant, + ProductVariantInterface $item2Variant, + ProductVariantInterface $item3Variant, + ProductVariantPriceCalculatorInterface $productVariantPriceCalculator, + ): void { + $item1->getVariant()->willReturn($item1Variant); + $item2->getVariant()->willReturn($item2Variant); + $item3->getVariant()->willReturn($item3Variant); + + $productVariantPriceCalculator->calculate($item1Variant, ['channel' => $channel])->willReturn(500); + $productVariantPriceCalculator->calculate($item2Variant, ['channel' => $channel])->willReturn(5000); + $productVariantPriceCalculator->calculate($item3Variant, ['channel' => $channel])->willReturn(10000); + + $this + ->filter([$item1, $item2, $item3], [ + 'filters' => ['price_range_filter' => ['max' => 1000]], + 'channel' => $channel, + ]) + ->shouldReturn([$item1]) + ; + } + + function it_filters_items_which_has_product_with_price_equal_to_configured_maximum_criteria( + ChannelInterface $channel, + OrderItemInterface $item1, + OrderItemInterface $item2, + ProductVariantInterface $item1Variant, + ProductVariantInterface $item2Variant, + ProductVariantPriceCalculatorInterface $productVariantPriceCalculator, + ): void { + $item1->getVariant()->willReturn($item1Variant); + $item2->getVariant()->willReturn($item2Variant); + + $productVariantPriceCalculator->calculate($item1Variant, ['channel' => $channel])->willReturn(500); + $productVariantPriceCalculator->calculate($item2Variant, ['channel' => $channel])->willReturn(1000); + + $this + ->filter([$item1, $item2], [ + 'filters' => ['price_range_filter' => ['max' => 1000]], + 'channel' => $channel, + ]) + ->shouldReturn([$item1, $item2]) + ; + } + function it_returns_all_items_if_configuration_is_invalid( ChannelInterface $channel, OrderItemInterface $item1, OrderItemInterface $item2, ): void { $this->filter([$item1, $item2], [])->shouldReturn([$item1, $item2]); - $this->filter([$item1, $item2], [ - 'filters' => ['price_range_filter' => ['max' => 10000]], - 'channel' => $channel, - ])->shouldReturn([$item1, $item2]); } function it_throws_exception_if_channel_is_not_configured(