mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-05 20:57:12 +00:00
Merge bae54d9620 into 9b6799e2b8
This commit is contained in:
commit
86a4678a1b
21 changed files with 1062 additions and 2 deletions
|
|
@ -166,6 +166,29 @@
|
|||
| `ShippingBundle\...\ShippingMethodCalculatorExists` | `invalidShippingCalculator` | `invalidShippingCalculatorMessage` |
|
||||
| `ShippingBundle\...\ShippingMethodRule` | `invalidType` | `invalidTypeMessage` |
|
||||
|
||||
## Promotion
|
||||
|
||||
1. New **opt-in per-channel** promotion rule and action types have been added. They let a single
|
||||
promotion rule/action hold independent configuration per channel, with the `configuration` array
|
||||
keyed by channel code (e.g. `['WEB_US' => ['count' => 2], 'WEB_GB' => ['count' => 5]]`):
|
||||
|
||||
- Rules: `cart_quantity_per_channel`, `customer_group_per_channel`, `nth_order_per_channel`,
|
||||
`shipping_country_per_channel`, `has_taxon_per_channel`, `contains_product_per_channel`.
|
||||
- Actions: `order_percentage_discount_per_channel`, `shipping_percentage_discount_per_channel`.
|
||||
|
||||
These are **additional** types registered alongside the existing plain ones. Existing promotions
|
||||
and the plain rule/action types are unchanged, and **no core service is replaced**, so the change
|
||||
is fully backward compatible and requires no action.
|
||||
|
||||
Implementation: two generic decorators in `Sylius\Component\Core\Promotion` unwrap the current
|
||||
channel's configuration slice and delegate to the standard checker/command:
|
||||
|
||||
- `Sylius\Component\Core\Promotion\Checker\Rule\PerChannelRuleChecker`
|
||||
- `Sylius\Component\Core\Promotion\Action\PerChannelPromotionActionCommand`
|
||||
|
||||
The admin forms use new `ChannelBased*ConfigurationType` form types built on top of
|
||||
`Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType`.
|
||||
|
||||
## Deprecations
|
||||
|
||||
1. Passing a `Sylius\Component\Core\Calculator\ProductVariantPricesCalculatorInterface` directly to the following catalog-facing classes is deprecated since Sylius 2.3.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
@receiving_discount
|
||||
Feature: Receiving discount with promotion rules and actions configured independently per channel
|
||||
In order to pay the proper amount while buying promoted goods with different rules and actions per channel
|
||||
As a Customer
|
||||
I want to have per-channel promotion rules and actions applied correctly in my current channel
|
||||
|
||||
Background:
|
||||
Given the store operates on a channel named "Web-US" in "USD" currency and with hostname "united.states"
|
||||
And the store operates on another channel named "Web-GB" in "GBP" currency and with hostname "great.britain"
|
||||
And the store classifies its products as "T-Shirts" and "Mugs"
|
||||
And the store has a product "PHP T-Shirt" priced at "$100.00" in "Web-US" channel
|
||||
And this product is also priced at "£80.00" in "Web-GB" channel
|
||||
And the product "PHP T-Shirt" belongs to taxon "T-Shirts"
|
||||
And the store has a product "Symfony T-Shirt" priced at "$60.00" in "Web-US" channel
|
||||
And this product is also priced at "£40.00" in "Web-GB" channel
|
||||
And there is a promotion "Holiday promotion"
|
||||
And I am a logged in customer
|
||||
|
||||
@api @todo-ui
|
||||
Scenario: Receiving an order percentage discount configured independently per channel
|
||||
Given the promotion gives "10%" discount to every order in the "Web-US" channel and "5%" discount to every order in the "Web-GB" channel
|
||||
When I changed my current channel to "Web-US"
|
||||
And I added product "PHP T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
Then my cart total should be "$90.00"
|
||||
And my discount should be "-$10.00"
|
||||
|
||||
@api @todo-ui
|
||||
Scenario: Order percentage discount applies the value configured for the current channel
|
||||
Given the promotion gives "10%" discount to every order in the "Web-US" channel and "5%" discount to every order in the "Web-GB" channel
|
||||
When I changed my current channel to "Web-GB"
|
||||
And I added product "PHP T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
Then my cart total should be "£76.00"
|
||||
And my discount should be "-£4.00"
|
||||
|
||||
@api @todo-ui
|
||||
Scenario: Receiving a discount when the cart contains the product configured for the current channel
|
||||
Given the promotion gives "$10.00" discount to every order in the "Web-US" channel and "£5.00" discount to every order in the "Web-GB" channel
|
||||
And the promotion applies to orders containing product "PHP T-Shirt" in the "Web-US" channel and product "Symfony T-Shirt" in the "Web-GB" channel
|
||||
When I changed my current channel to "Web-US"
|
||||
And I added product "PHP T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
Then my cart total should be "$90.00"
|
||||
And my discount should be "-$10.00"
|
||||
|
||||
@api @todo-ui
|
||||
Scenario: Receiving a discount when the cart has a product from the taxon configured for the current channel
|
||||
Given the promotion gives "$10.00" discount to every order in the "Web-US" channel and "£5.00" discount to every order in the "Web-GB" channel
|
||||
And the promotion applies to orders with a product from taxon "T-Shirts" in the "Web-US" channel and from taxon "Mugs" in the "Web-GB" channel
|
||||
When I changed my current channel to "Web-US"
|
||||
And I added product "PHP T-Shirt" to the cart
|
||||
And I check the details of my cart
|
||||
Then my cart total should be "$90.00"
|
||||
And my discount should be "-$10.00"
|
||||
|
|
@ -404,6 +404,72 @@ final readonly class PromotionContext implements Context
|
|||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
#[Given('/^([^"]+) gives ("[^"]+%") discount to every order in the ("[^"]+" channel) and ("[^"]+%") discount to every order in the ("[^"]+" channel)$/')]
|
||||
public function thisPromotionGivesPercentageDiscountToEveryOrderInTheChannelAndInTheChannel(
|
||||
PromotionInterface $promotion,
|
||||
float $firstDiscount,
|
||||
ChannelInterface $firstChannel,
|
||||
float $secondDiscount,
|
||||
ChannelInterface $secondChannel,
|
||||
): void {
|
||||
$action = $this->actionFactory->createNew();
|
||||
$action->setType('order_percentage_discount_per_channel');
|
||||
$action->setConfiguration([
|
||||
$firstChannel->getCode() => ['percentage' => $firstDiscount],
|
||||
$secondChannel->getCode() => ['percentage' => $secondDiscount],
|
||||
]);
|
||||
|
||||
$promotion->addChannel($firstChannel);
|
||||
$promotion->addChannel($secondChannel);
|
||||
$promotion->addAction($action);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
#[Given('/^(the promotion) applies to orders containing (product "[^"]+") in the ("[^"]+" channel) and (product "[^"]+") in the ("[^"]+" channel)$/')]
|
||||
public function thePromotionAppliesToOrdersContainingProductInTheChannelAndInTheChannel(
|
||||
PromotionInterface $promotion,
|
||||
ProductInterface $firstProduct,
|
||||
ChannelInterface $firstChannel,
|
||||
ProductInterface $secondProduct,
|
||||
ChannelInterface $secondChannel,
|
||||
): void {
|
||||
$rule = $this->ruleFactory->createNew();
|
||||
$rule->setType('contains_product_per_channel');
|
||||
$rule->setConfiguration([
|
||||
$firstChannel->getCode() => ['product_code' => $firstProduct->getCode()],
|
||||
$secondChannel->getCode() => ['product_code' => $secondProduct->getCode()],
|
||||
]);
|
||||
|
||||
$promotion->addChannel($firstChannel);
|
||||
$promotion->addChannel($secondChannel);
|
||||
$promotion->addRule($rule);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
#[Given('/^(the promotion) applies to orders with a product from (taxon "[^"]+") in the ("[^"]+" channel) and from (taxon "[^"]+") in the ("[^"]+" channel)$/')]
|
||||
public function thePromotionAppliesToOrdersWithAProductFromTaxonInTheChannelAndInTheChannel(
|
||||
PromotionInterface $promotion,
|
||||
TaxonInterface $firstTaxon,
|
||||
ChannelInterface $firstChannel,
|
||||
TaxonInterface $secondTaxon,
|
||||
ChannelInterface $secondChannel,
|
||||
): void {
|
||||
$rule = $this->ruleFactory->createNew();
|
||||
$rule->setType('has_taxon_per_channel');
|
||||
$rule->setConfiguration([
|
||||
$firstChannel->getCode() => ['taxons' => [$firstTaxon->getCode()]],
|
||||
$secondChannel->getCode() => ['taxons' => [$secondTaxon->getCode()]],
|
||||
]);
|
||||
|
||||
$promotion->addChannel($firstChannel);
|
||||
$promotion->addChannel($secondChannel);
|
||||
$promotion->addRule($rule);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
#[Given('/^(this promotion) gives ("(?:€|£|\$)[^"]+") off on every product in the ("[^"]+" channel) and ("(?:€|£|\$)[^"]+") off in the ("[^"]+" channel)$/')]
|
||||
public function thisPromotionGivesFixedDiscountOnEveryProductInTheChannelAndInTheChannel(
|
||||
PromotionInterface $promotion,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Action;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Bundle\PromotionBundle\Form\Type\Action\PercentageDiscountConfigurationType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedPercentageDiscountConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => PercentageDiscountConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Bundle\PromotionBundle\Form\Type\Rule\CartQuantityConfigurationType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedCartQuantityConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => CartQuantityConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedContainsProductConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => ContainsProductConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedCustomerGroupConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => CustomerGroupConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedHasTaxonConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => HasTaxonConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedNthOrderConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => NthOrderConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ChannelBasedShippingCountryConfigurationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => ShippingCountryConfigurationType::class,
|
||||
'entry_options' => fn (ChannelInterface $channel) => [
|
||||
'label' => $channel->getName(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChannelCollectionType::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,9 +44,15 @@ sylius_promotion:
|
|||
order_percentage_discount:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_action_order_percentage_discount'
|
||||
order_percentage_discount_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_action_order_percentage_discount_per_channel'
|
||||
shipping_percentage_discount:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_action_shipping_percentage_discount'
|
||||
shipping_percentage_discount_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_action_shipping_percentage_discount_per_channel'
|
||||
order_fixed_discount:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_action_order_fixed_discount'
|
||||
|
|
@ -62,18 +68,36 @@ sylius_promotion:
|
|||
customer_group:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_customer_group'
|
||||
customer_group_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_customer_group_per_channel'
|
||||
nth_order:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_nth_order'
|
||||
nth_order_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_nth_order_per_channel'
|
||||
shipping_country:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_shipping_country'
|
||||
shipping_country_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_shipping_country_per_channel'
|
||||
has_taxon:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_has_taxon'
|
||||
has_taxon_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_has_taxon_per_channel'
|
||||
total_of_items_from_taxon:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_total_of_items_from_taxon'
|
||||
contains_product:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_contains_product'
|
||||
contains_product_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_contains_product_per_channel'
|
||||
cart_quantity_per_channel:
|
||||
- 'sylius'
|
||||
- 'sylius_promotion_rule_cart_quantity_per_channel'
|
||||
|
|
|
|||
|
|
@ -15,9 +15,16 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
|||
|
||||
use Sylius\Bundle\CoreBundle\Doctrine\ORM\Promotion\Modifier\AtomicOrderPromotionsUsageModifier;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Action\ChannelBasedFixedDiscountConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Action\ChannelBasedPercentageDiscountConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Action\ChannelBasedUnitFixedDiscountConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Action\ChannelBasedUnitPercentageDiscountConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedCartQuantityConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedContainsProductConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedCustomerGroupConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedHasTaxonConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedItemTotalConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedNthOrderConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedShippingCountryConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ChannelBasedTotalOfItemsFromTaxonConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\ContainsProductConfigurationType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Promotion\Rule\CustomerGroupConfigurationType;
|
||||
|
|
@ -32,6 +39,7 @@ use Sylius\Component\Core\Factory\PromotionRuleFactory;
|
|||
use Sylius\Component\Core\Factory\PromotionRuleFactoryInterface;
|
||||
use Sylius\Component\Core\Promotion\Action\FixedDiscountPromotionActionCommand;
|
||||
use Sylius\Component\Core\Promotion\Action\PercentageDiscountPromotionActionCommand;
|
||||
use Sylius\Component\Core\Promotion\Action\PerChannelPromotionActionCommand;
|
||||
use Sylius\Component\Core\Promotion\Action\ShippingPercentageDiscountPromotionActionCommand;
|
||||
use Sylius\Component\Core\Promotion\Action\UnitFixedDiscountPromotionActionCommand;
|
||||
use Sylius\Component\Core\Promotion\Action\UnitPercentageDiscountPromotionActionCommand;
|
||||
|
|
@ -46,6 +54,7 @@ use Sylius\Component\Core\Promotion\Checker\Rule\CustomerGroupRuleChecker;
|
|||
use Sylius\Component\Core\Promotion\Checker\Rule\HasTaxonRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\ItemTotalRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\NthOrderRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\PerChannelRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\ShippingCountryRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\TotalOfItemsFromTaxonRuleChecker;
|
||||
use Sylius\Component\Core\Promotion\Checker\TaxonInPromotionRuleChecker;
|
||||
|
|
@ -85,23 +94,47 @@ return static function (ContainerConfigurator $container) {
|
|||
->tag('sylius.promotion_rule_checker', ['type' => 'customer_group', 'label' => 'sylius.form.promotion_rule.customer_group', 'form_type' => CustomerGroupConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.customer_group_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.customer_group')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'customer_group_per_channel', 'label' => 'sylius.form.promotion_rule.customer_group_per_channel', 'form_type' => ChannelBasedCustomerGroupConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.nth_order', NthOrderRuleChecker::class)
|
||||
->args([service('sylius.repository.order')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'nth_order', 'label' => 'sylius.form.promotion_rule.nth_order', 'form_type' => NthOrderConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.nth_order_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.nth_order')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'nth_order_per_channel', 'label' => 'sylius.form.promotion_rule.nth_order_per_channel', 'form_type' => ChannelBasedNthOrderConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.shipping_country', ShippingCountryRuleChecker::class)
|
||||
->args([service('sylius.repository.country')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'shipping_country', 'label' => 'sylius.form.promotion_rule.shipping_country', 'form_type' => ShippingCountryConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.shipping_country_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.shipping_country')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'shipping_country_per_channel', 'label' => 'sylius.form.promotion_rule.shipping_country_per_channel', 'form_type' => ChannelBasedShippingCountryConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.has_taxon', HasTaxonRuleChecker::class)
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'has_taxon', 'label' => 'sylius.form.promotion_rule.has_at_least_one_from_taxons', 'form_type' => HasTaxonConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.has_taxon_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.has_taxon')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'has_taxon_per_channel', 'label' => 'sylius.form.promotion_rule.has_at_least_one_from_taxons_per_channel', 'form_type' => ChannelBasedHasTaxonConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.total_of_items_from_taxon', TotalOfItemsFromTaxonRuleChecker::class)
|
||||
->args([service('sylius.repository.taxon')])
|
||||
|
|
@ -113,6 +146,12 @@ return static function (ContainerConfigurator $container) {
|
|||
->tag('sylius.promotion_rule_checker', ['type' => 'contains_product', 'label' => 'sylius.form.promotion_rule.contains_product', 'form_type' => ContainsProductConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.contains_product_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.contains_product')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'contains_product_per_channel', 'label' => 'sylius.form.promotion_rule.contains_product_per_channel', 'form_type' => ChannelBasedContainsProductConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.item_total', ItemTotalRuleChecker::class)
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'item_total', 'label' => 'sylius.form.promotion_rule.item_total', 'form_type' => ChannelBasedItemTotalConfigurationType::class])
|
||||
|
|
@ -123,6 +162,12 @@ return static function (ContainerConfigurator $container) {
|
|||
->tag('sylius.promotion_rule_checker', ['type' => 'cart_quantity', 'label' => 'sylius.form.promotion_rule.cart_quantity', 'form_type' => CartQuantityConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion_rule.cart_quantity_per_channel', PerChannelRuleChecker::class)
|
||||
->args([service('sylius.checker.promotion_rule.cart_quantity')])
|
||||
->tag('sylius.promotion_rule_checker', ['type' => 'cart_quantity_per_channel', 'label' => 'sylius.form.promotion_rule.cart_quantity_per_channel', 'form_type' => ChannelBasedCartQuantityConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.command.promotion_action.fixed_discount', FixedDiscountPromotionActionCommand::class)
|
||||
->args([
|
||||
|
|
@ -156,6 +201,12 @@ return static function (ContainerConfigurator $container) {
|
|||
->tag('sylius.promotion_action', ['type' => 'order_percentage_discount', 'label' => 'sylius.form.promotion_action.order_percentage_discount', 'form_type' => PercentageDiscountConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.command.promotion_action.percentage_discount_per_channel', PerChannelPromotionActionCommand::class)
|
||||
->args([service('sylius.command.promotion_action.percentage_discount')])
|
||||
->tag('sylius.promotion_action', ['type' => 'order_percentage_discount_per_channel', 'label' => 'sylius.form.promotion_action.order_percentage_discount_per_channel', 'form_type' => ChannelBasedPercentageDiscountConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.command.promotion_action.unit_percentage_discount', UnitPercentageDiscountPromotionActionCommand::class)
|
||||
->args([
|
||||
|
|
@ -173,6 +224,12 @@ return static function (ContainerConfigurator $container) {
|
|||
->tag('sylius.promotion_action', ['type' => 'shipping_percentage_discount', 'label' => 'sylius.form.promotion_action.shipping_percentage_discount', 'form_type' => PercentageDiscountConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.command.promotion_action.shipping_percentage_discount_per_channel', PerChannelPromotionActionCommand::class)
|
||||
->args([service('sylius.command.promotion_action.shipping_percentage_discount')])
|
||||
->tag('sylius.promotion_action', ['type' => 'shipping_percentage_discount_per_channel', 'label' => 'sylius.form.promotion_action.shipping_percentage_discount_per_channel', 'form_type' => ChannelBasedPercentageDiscountConfigurationType::class])
|
||||
;
|
||||
|
||||
$services
|
||||
->set('sylius.checker.promotion.promotion_coupon_per_customer_usage_limit_eligibility', PromotionCouponPerCustomerUsageLimitEligibilityChecker::class)
|
||||
->args([service('sylius.repository.order')])
|
||||
|
|
|
|||
|
|
@ -280,6 +280,46 @@
|
|||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">
|
||||
<value>sylius_promotion_action_order_percentage_discount_per_channel</value>
|
||||
<value>sylius_promotion_action_shipping_percentage_discount_per_channel</value>
|
||||
</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="fields">
|
||||
<value key="percentage">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">
|
||||
<value>sylius_promotion_action_order_percentage_discount_per_channel</value>
|
||||
<value>sylius_promotion_action_shipping_percentage_discount_per_channel</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">numeric</option>
|
||||
<option name="groups">
|
||||
<value>sylius_promotion_action_order_percentage_discount_per_channel</value>
|
||||
<value>sylius_promotion_action_shipping_percentage_discount_per_channel</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Range">
|
||||
<option name="min">0</option>
|
||||
<option name="max">1</option>
|
||||
<option name="notInRangeMessage">sylius.promotion_action.percentage_discount_configuration.not_in_range</option>
|
||||
<option name="groups">
|
||||
<value>sylius_promotion_action_order_percentage_discount_per_channel</value>
|
||||
<value>sylius_promotion_action_shipping_percentage_discount_per_channel</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
|
|||
|
|
@ -178,6 +178,163 @@
|
|||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_cart_quantity_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_cart_quantity_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="count">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_cart_quantity_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">numeric</option>
|
||||
<option name="groups">sylius_promotion_rule_cart_quantity_per_channel</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_customer_group_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_customer_group_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="group_code">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_customer_group_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">string</option>
|
||||
<option name="groups">sylius_promotion_rule_customer_group_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\CustomerGroupCodeExists">
|
||||
<option name="groups">sylius_promotion_rule_customer_group_per_channel</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_nth_order_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_nth_order_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="nth">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_nth_order_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">numeric</option>
|
||||
<option name="groups">sylius_promotion_rule_nth_order_per_channel</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_shipping_country_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_shipping_country_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="country">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_shipping_country_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">string</option>
|
||||
<option name="groups">sylius_promotion_rule_shipping_country_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\CountryCodeExists">
|
||||
<option name="groups">sylius_promotion_rule_shipping_country_per_channel</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_has_taxon_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_has_taxon_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="taxons">
|
||||
<constraint name="All">
|
||||
<option name="groups">sylius_promotion_rule_has_taxon_per_channel</option>
|
||||
<option name="constraints">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_has_taxon_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\TaxonCodeExists">
|
||||
<option name="groups">sylius_promotion_rule_has_taxon_per_channel</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_contains_product_per_channel</option>
|
||||
<option name="validateAgainstAllChannels">true</option>
|
||||
<option name="channelAwarePropertyPath">promotion</option>
|
||||
<option name="constraints">
|
||||
<constraint name="Collection">
|
||||
<option name="allowExtraFields">true</option>
|
||||
<option name="groups">sylius_promotion_rule_contains_product_per_channel</option>
|
||||
<option name="fields">
|
||||
<value key="product_code">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">sylius_promotion_rule_contains_product_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Type">
|
||||
<option name="type">string</option>
|
||||
<option name="groups">sylius_promotion_rule_contains_product_per_channel</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\ProductCodeExists">
|
||||
<option name="groups">sylius_promotion_rule_contains_product_per_channel</option>
|
||||
</constraint>
|
||||
</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</option>
|
||||
</constraint>
|
||||
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ sylius:
|
|||
item_percentage_discount: Item percentage discount
|
||||
order_fixed_discount: Order fixed discount
|
||||
order_percentage_discount: Order percentage discount
|
||||
order_percentage_discount_per_channel: Order percentage discount (per channel)
|
||||
shipping_percentage_discount: Shipping percentage discount
|
||||
shipping_percentage_discount_per_channel: Shipping percentage discount (per channel)
|
||||
filters: Filters
|
||||
fixed_discount_configuration:
|
||||
amount: Amount
|
||||
|
|
@ -56,12 +58,18 @@ sylius:
|
|||
price_range: Price range filter
|
||||
promotion_rule:
|
||||
cart_quantity: Cart quantity
|
||||
cart_quantity_per_channel: Cart quantity (per channel)
|
||||
contains_product: Contains product
|
||||
contains_product_per_channel: Contains product (per channel)
|
||||
customer_group: Customer group
|
||||
customer_group_per_channel: Customer group (per channel)
|
||||
has_at_least_one_from_taxons: Has at least one from taxons
|
||||
has_at_least_one_from_taxons_per_channel: Has at least one from taxons (per channel)
|
||||
item_total: Item total
|
||||
nth_order: Nth order
|
||||
nth_order_per_channel: Nth order (per channel)
|
||||
shipping_country: Shipping country
|
||||
shipping_country_per_channel: Shipping country (per channel)
|
||||
taxonomy: Taxonomy
|
||||
total_price_of_items_from_taxon: Total price of items from taxon
|
||||
cart_quantity_configuration:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Component\Core\Promotion\Action;
|
||||
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Promotion\Action\PromotionActionCommandInterface;
|
||||
use Sylius\Component\Promotion\Model\PromotionInterface;
|
||||
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
|
||||
use Sylius\Resource\Exception\UnexpectedTypeException;
|
||||
|
||||
final class PerChannelPromotionActionCommand implements PromotionActionCommandInterface
|
||||
{
|
||||
public function __construct(private PromotionActionCommandInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $configuration */
|
||||
public function execute(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): bool
|
||||
{
|
||||
if (!$subject instanceof OrderInterface) {
|
||||
throw new UnexpectedTypeException($subject, OrderInterface::class);
|
||||
}
|
||||
|
||||
$channel = $subject->getChannel();
|
||||
if ($channel === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$channelCode = $channel->getCode();
|
||||
if (!isset($configuration[$channelCode]) || !is_array($configuration[$channelCode])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decorated->execute($subject, $configuration[$channelCode], $promotion);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $configuration */
|
||||
public function revert(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): void
|
||||
{
|
||||
if (!$subject instanceof OrderInterface) {
|
||||
throw new UnexpectedTypeException($subject, OrderInterface::class);
|
||||
}
|
||||
|
||||
$channel = $subject->getChannel();
|
||||
if ($channel === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$channelCode = $channel->getCode();
|
||||
if (!isset($configuration[$channelCode]) || !is_array($configuration[$channelCode])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->decorated->revert($subject, $configuration[$channelCode], $promotion);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Component\Core\Promotion\Checker\Rule;
|
||||
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Promotion\Checker\Rule\RuleCheckerInterface;
|
||||
use Sylius\Component\Promotion\Exception\UnsupportedTypeException;
|
||||
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
|
||||
|
||||
final class PerChannelRuleChecker implements RuleCheckerInterface
|
||||
{
|
||||
public function __construct(private RuleCheckerInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $configuration
|
||||
*
|
||||
* @throws UnsupportedTypeException
|
||||
*/
|
||||
public function isEligible(PromotionSubjectInterface $subject, array $configuration): bool
|
||||
{
|
||||
if (!$subject instanceof OrderInterface) {
|
||||
throw new UnsupportedTypeException($subject, OrderInterface::class);
|
||||
}
|
||||
|
||||
$channel = $subject->getChannel();
|
||||
if ($channel === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$channelCode = $channel->getCode();
|
||||
if (!isset($configuration[$channelCode]) || !is_array($configuration[$channelCode])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->decorated->isEligible($subject, $configuration[$channelCode]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Component\Core\Promotion\Action;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Promotion\Action\PerChannelPromotionActionCommand;
|
||||
use Sylius\Component\Promotion\Action\PromotionActionCommandInterface;
|
||||
use Sylius\Component\Promotion\Model\PromotionInterface;
|
||||
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
|
||||
use Sylius\Resource\Exception\UnexpectedTypeException;
|
||||
|
||||
#[CoversClass(PerChannelPromotionActionCommand::class)]
|
||||
final class PerChannelPromotionActionCommandTest extends TestCase
|
||||
{
|
||||
private MockObject&PromotionActionCommandInterface $decorated;
|
||||
|
||||
private ChannelInterface&MockObject $channel;
|
||||
|
||||
private MockObject&OrderInterface $order;
|
||||
|
||||
private MockObject&PromotionInterface $promotion;
|
||||
|
||||
private PerChannelPromotionActionCommand $command;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->decorated = $this->createMock(PromotionActionCommandInterface::class);
|
||||
$this->channel = $this->createMock(ChannelInterface::class);
|
||||
$this->order = $this->createMock(OrderInterface::class);
|
||||
$this->promotion = $this->createMock(PromotionInterface::class);
|
||||
$this->command = new PerChannelPromotionActionCommand($this->decorated);
|
||||
}
|
||||
|
||||
public function testShouldImplementPromotionActionCommandInterface(): void
|
||||
{
|
||||
$this->assertInstanceOf(PromotionActionCommandInterface::class, $this->command);
|
||||
}
|
||||
|
||||
public function testShouldExecuteDecoratedCommandWithTheChannelConfiguration(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->once())
|
||||
->method('execute')
|
||||
->with($this->order, ['percentage' => 0.2], $this->promotion)
|
||||
->willReturn(true)
|
||||
;
|
||||
|
||||
$this->assertTrue($this->command->execute($this->order, ['WEB_US' => ['percentage' => 0.2]], $this->promotion));
|
||||
}
|
||||
|
||||
public function testShouldNotExecuteDecoratedCommandIfThereIsNoConfigurationForTheOrderChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('execute');
|
||||
|
||||
$this->assertFalse($this->command->execute($this->order, ['WEB_EU' => ['percentage' => 0.2]], $this->promotion));
|
||||
}
|
||||
|
||||
public function testShouldNotExecuteDecoratedCommandWhenOrderHasNoChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn(null);
|
||||
$this->decorated->expects($this->never())->method('execute');
|
||||
|
||||
$this->assertFalse($this->command->execute($this->order, ['WEB_US' => ['percentage' => 0.2]], $this->promotion));
|
||||
}
|
||||
|
||||
public function testShouldNotExecuteDecoratedCommandWhenChannelConfigurationIsNotAnArray(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('execute');
|
||||
|
||||
$this->assertFalse($this->command->execute($this->order, ['WEB_US' => 'not-an-array'], $this->promotion));
|
||||
}
|
||||
|
||||
public function testShouldThrowExceptionWhenExecutingIfPromotionSubjectIsNotOrder(): void
|
||||
{
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
|
||||
$this->command->execute($this->createMock(PromotionSubjectInterface::class), [], $this->promotion);
|
||||
}
|
||||
|
||||
public function testShouldRevertDecoratedCommandWithTheChannelConfiguration(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->once())
|
||||
->method('revert')
|
||||
->with($this->order, ['percentage' => 0.2], $this->promotion)
|
||||
;
|
||||
|
||||
$this->command->revert($this->order, ['WEB_US' => ['percentage' => 0.2]], $this->promotion);
|
||||
}
|
||||
|
||||
public function testShouldNotRevertDecoratedCommandIfThereIsNoConfigurationForTheOrderChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('revert');
|
||||
|
||||
$this->command->revert($this->order, ['WEB_EU' => ['percentage' => 0.2]], $this->promotion);
|
||||
}
|
||||
|
||||
public function testShouldNotRevertDecoratedCommandWhenOrderHasNoChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn(null);
|
||||
$this->decorated->expects($this->never())->method('revert');
|
||||
|
||||
$this->command->revert($this->order, ['WEB_US' => ['percentage' => 0.2]], $this->promotion);
|
||||
}
|
||||
|
||||
public function testShouldNotRevertDecoratedCommandWhenChannelConfigurationIsNotAnArray(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('revert');
|
||||
|
||||
$this->command->revert($this->order, ['WEB_US' => 'not-an-array'], $this->promotion);
|
||||
}
|
||||
|
||||
public function testShouldThrowExceptionWhenRevertingIfPromotionSubjectIsNotOrder(): void
|
||||
{
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
|
||||
$this->command->revert($this->createMock(PromotionSubjectInterface::class), [], $this->promotion);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Sylius\Component\Core\Promotion\Checker\Rule;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Promotion\Checker\Rule\PerChannelRuleChecker;
|
||||
use Sylius\Component\Promotion\Checker\Rule\RuleCheckerInterface;
|
||||
use Sylius\Component\Promotion\Exception\UnsupportedTypeException;
|
||||
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
|
||||
|
||||
#[CoversClass(PerChannelRuleChecker::class)]
|
||||
final class PerChannelRuleCheckerTest extends TestCase
|
||||
{
|
||||
private MockObject&RuleCheckerInterface $decorated;
|
||||
|
||||
private ChannelInterface&MockObject $channel;
|
||||
|
||||
private MockObject&OrderInterface $order;
|
||||
|
||||
private PerChannelRuleChecker $ruleChecker;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->decorated = $this->createMock(RuleCheckerInterface::class);
|
||||
$this->channel = $this->createMock(ChannelInterface::class);
|
||||
$this->order = $this->createMock(OrderInterface::class);
|
||||
$this->ruleChecker = new PerChannelRuleChecker($this->decorated);
|
||||
}
|
||||
|
||||
public function testShouldImplementRuleCheckerInterface(): void
|
||||
{
|
||||
$this->assertInstanceOf(RuleCheckerInterface::class, $this->ruleChecker);
|
||||
}
|
||||
|
||||
public function testShouldDelegateToDecoratedCheckerWithTheChannelConfiguration(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->once())
|
||||
->method('isEligible')
|
||||
->with($this->order, ['nth' => 5])
|
||||
->willReturn(true)
|
||||
;
|
||||
|
||||
$this->assertTrue($this->ruleChecker->isEligible($this->order, ['WEB_US' => ['nth' => 5]]));
|
||||
}
|
||||
|
||||
public function testShouldReturnDecoratedCheckerResultForTheOrderChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->once())
|
||||
->method('isEligible')
|
||||
->with($this->order, ['nth' => 5])
|
||||
->willReturn(false)
|
||||
;
|
||||
|
||||
$this->assertFalse($this->ruleChecker->isEligible($this->order, ['WEB_US' => ['nth' => 5]]));
|
||||
}
|
||||
|
||||
public function testShouldReturnFalseAndNotDelegateIfThereIsNoConfigurationForTheOrderChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('isEligible');
|
||||
|
||||
$this->assertFalse($this->ruleChecker->isEligible($this->order, ['WEB_EU' => ['nth' => 5]]));
|
||||
}
|
||||
|
||||
public function testShouldReturnFalseAndNotDelegateWhenOrderHasNoChannel(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn(null);
|
||||
$this->decorated->expects($this->never())->method('isEligible');
|
||||
|
||||
$this->assertFalse($this->ruleChecker->isEligible($this->order, ['WEB_US' => ['nth' => 5]]));
|
||||
}
|
||||
|
||||
public function testShouldReturnFalseAndNotDelegateWhenChannelConfigurationIsNotAnArray(): void
|
||||
{
|
||||
$this->order->expects($this->once())->method('getChannel')->willReturn($this->channel);
|
||||
$this->channel->expects($this->once())->method('getCode')->willReturn('WEB_US');
|
||||
$this->decorated->expects($this->never())->method('isEligible');
|
||||
|
||||
$this->assertFalse($this->ruleChecker->isEligible($this->order, ['WEB_US' => 'not-an-array']));
|
||||
}
|
||||
|
||||
public function testShouldThrowExceptionIfPromotionSubjectIsNotOrder(): void
|
||||
{
|
||||
$this->expectException(UnsupportedTypeException::class);
|
||||
|
||||
$this->ruleChecker->isEligible($this->createMock(PromotionSubjectInterface::class), []);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
},
|
||||
{
|
||||
"propertyPath": "actions[5].type",
|
||||
"message": "Promotion action type is invalid. Available action types are order_fixed_discount, unit_fixed_discount, order_percentage_discount, unit_percentage_discount, shipping_percentage_discount.",
|
||||
"message": "Promotion action type is invalid. Available action types are order_fixed_discount, unit_fixed_discount, order_percentage_discount, order_percentage_discount_per_channel, unit_percentage_discount, shipping_percentage_discount, shipping_percentage_discount_per_channel.",
|
||||
"code": null
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
},
|
||||
{
|
||||
"propertyPath": "rules[8].type",
|
||||
"message": "Promotion rule type is invalid. Available rule types are customer_group, nth_order, shipping_country, has_taxon, total_of_items_from_taxon, contains_product, item_total, cart_quantity.",
|
||||
"message": "Promotion rule type is invalid. Available rule types are customer_group, customer_group_per_channel, nth_order, nth_order_per_channel, shipping_country, shipping_country_per_channel, has_taxon, has_taxon_per_channel, total_of_items_from_taxon, contains_product, contains_product_per_channel, item_total, cart_quantity, cart_quantity_per_channel.",
|
||||
"code": null
|
||||
}
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue