From 86ebce9402dafb1015cb8be28a5e04ba14b24453 Mon Sep 17 00:00:00 2001 From: tleon Date: Mon, 27 Jan 2025 16:32:39 +0100 Subject: [PATCH] chore(CQRS): add new create free shipping discount command --- classes/CartRule.php | 6 +- install-dev/data/db_structure.sql | 4 +- src/Adapter/CartRule/CartRuleBuilder.php | 60 +++++ .../AddFreeShippingDiscountHandler.php | 52 ++++ .../GetDiscountForEditingHandler.php | 72 +++++ .../Discount/Command/AddDiscountCommand.php | 245 ++++++++++++++++++ .../AddFreeShippingDiscountCommand.php | 37 +++ ...ddFreeShippingDiscountHandlerInterface.php | 35 +++ .../Exception/DiscountConstraintException.php | 76 ++++++ .../Discount/Exception/DiscountException.php | 33 +++ .../Exception/DiscountNotFoundException.php | 31 +++ .../Discount/Query/GetDiscountForEditing.php | 39 +++ .../GetDiscountForEditingHandlerInterface.php | 35 +++ .../QueryResult/DiscountForEditing.php | 117 +++++++++ .../Discount/ValueObject/DiscountId.php | 57 ++++ .../Discount/ValueObject/DiscountType.php | 44 ++++ .../Discount/ValueObject/GiftedProduct.php | 53 ++++ .../ValueObject/PercentageDiscount.php | 48 ++++ .../config/services/adapter/cart_rule.yml | 5 + .../config/services/adapter/discount.yml | 13 + .../AbstractDiscountFeatureContext.php | 159 ++++++++++++ .../Discount/AddDiscountFeatureContext.php | 66 +++++ .../Scenario/Discount/add_discount.feature | 43 +++ tests/Integration/Behaviour/behat.yml | 72 ++--- 24 files changed, 1368 insertions(+), 34 deletions(-) create mode 100644 src/Adapter/CartRule/CartRuleBuilder.php create mode 100644 src/Adapter/Discount/CommandHandler/AddFreeShippingDiscountHandler.php create mode 100644 src/Adapter/Discount/QueryHandler/GetDiscountForEditingHandler.php create mode 100644 src/Core/Domain/Discount/Command/AddDiscountCommand.php create mode 100644 src/Core/Domain/Discount/Command/AddFreeShippingDiscountCommand.php create mode 100644 src/Core/Domain/Discount/CommandHandler/AddFreeShippingDiscountHandlerInterface.php create mode 100644 src/Core/Domain/Discount/Exception/DiscountConstraintException.php create mode 100644 src/Core/Domain/Discount/Exception/DiscountException.php create mode 100644 src/Core/Domain/Discount/Exception/DiscountNotFoundException.php create mode 100644 src/Core/Domain/Discount/Query/GetDiscountForEditing.php create mode 100644 src/Core/Domain/Discount/QueryHandler/GetDiscountForEditingHandlerInterface.php create mode 100644 src/Core/Domain/Discount/QueryResult/DiscountForEditing.php create mode 100644 src/Core/Domain/Discount/ValueObject/DiscountId.php create mode 100644 src/Core/Domain/Discount/ValueObject/DiscountType.php create mode 100644 src/Core/Domain/Discount/ValueObject/GiftedProduct.php create mode 100644 src/Core/Domain/Discount/ValueObject/PercentageDiscount.php create mode 100644 src/PrestaShopBundle/Resources/config/services/adapter/discount.yml create mode 100644 tests/Integration/Behaviour/Features/Context/Domain/Discount/AbstractDiscountFeatureContext.php create mode 100644 tests/Integration/Behaviour/Features/Context/Domain/Discount/AddDiscountFeatureContext.php create mode 100644 tests/Integration/Behaviour/Features/Scenario/Discount/add_discount.feature diff --git a/classes/CartRule.php b/classes/CartRule.php index 3ee8bfadc3c88..18ff4cbb6a098 100644 --- a/classes/CartRule.php +++ b/classes/CartRule.php @@ -105,6 +105,7 @@ class CartRuleCore extends ObjectModel public $active = true; public $date_add; public $date_upd; + public string $type = ''; protected static $cartAmountCache = []; @@ -148,6 +149,7 @@ class CartRuleCore extends ObjectModel 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + 'type' => ['type' => self::TYPE_STRING, 'validate' => 'isString'], /* Lang fields */ 'name' => [ 'type' => self::TYPE_HTML, @@ -511,8 +513,8 @@ public static function getCustomerCartRules( * in the cart rule. So he will be able to use it. */ $validAddressExists = Db::getInstance()->getValue(' - SELECT crc.id_cart_rule - FROM ' . _DB_PREFIX_ . 'cart_rule_country crc + SELECT crc.id_cart_rule + FROM ' . _DB_PREFIX_ . 'cart_rule_country crc INNER JOIN ' . _DB_PREFIX_ . 'address a ON a.id_customer = ' . (int) $id_customer . ' AND a.deleted = 0 AND diff --git a/install-dev/data/db_structure.sql b/install-dev/data/db_structure.sql index f6e09a44b2062..a39dc882c53b5 100644 --- a/install-dev/data/db_structure.sql +++ b/install-dev/data/db_structure.sql @@ -197,6 +197,7 @@ CREATE TABLE `PREFIX_cart_rule` ( `active` tinyint(1) unsigned NOT NULL DEFAULT '0', `date_add` datetime NOT NULL, `date_upd` datetime NOT NULL, + `type` varchar(128) DEFAULT NULL, PRIMARY KEY (`id_cart_rule`), KEY `id_customer` ( `id_customer`, `active`, `date_to` @@ -213,7 +214,8 @@ CREATE TABLE `PREFIX_cart_rule` ( `date_to` ), KEY `date_from` (`date_from`), - KEY `date_to` (`date_to`) + KEY `date_to` (`date_to`), + KEY `type` (`type`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4 COLLATION; /* Localized name assocatied with a promo code */ diff --git a/src/Adapter/CartRule/CartRuleBuilder.php b/src/Adapter/CartRule/CartRuleBuilder.php new file mode 100644 index 0000000000000..73941be8b300e --- /dev/null +++ b/src/Adapter/CartRule/CartRuleBuilder.php @@ -0,0 +1,60 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\CartRule; + +use CartRule; +use DateTimeImmutable; +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddDiscountCommand; +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddFreeShippingDiscountCommand; +use PrestaShop\PrestaShop\Core\Util\DateTime\DateTime as DateTimeUtil; + +class CartRuleBuilder +{ + public function build(AddDiscountCommand $command): CartRule + { + $cartRule = new CartRule(); + $validFrom = $command->getValidFrom() ?: new DateTimeImmutable(); + $validTo = $command->getValidTo() ?: $validFrom->modify('+1 month'); + + $cartRule->name = $command->getLocalizedNames(); + $cartRule->description = $command->getDescription(); + $cartRule->code = $command->getCode(); + $cartRule->highlight = $command->isHighlightInCart(); + $cartRule->partial_use = $command->allowPartialUse(); + $cartRule->priority = $command->getPriority(); + $cartRule->active = $command->isActive(); + $cartRule->id_customer = $command->getCustomerId()?->getValue(); + $cartRule->date_from = $validFrom->format(DateTimeUtil::DEFAULT_DATETIME_FORMAT); + $cartRule->date_to = $validTo->format(DateTimeUtil::DEFAULT_DATETIME_FORMAT); + $cartRule->quantity = $command->getTotalQuantity(); + $cartRule->quantity_per_user = $command->getQuantityPerUser(); + $cartRule->type = $command->getDiscountType()->getValue(); + $cartRule->free_shipping = $command instanceof AddFreeShippingDiscountCommand; + + return $cartRule; + } +} diff --git a/src/Adapter/Discount/CommandHandler/AddFreeShippingDiscountHandler.php b/src/Adapter/Discount/CommandHandler/AddFreeShippingDiscountHandler.php new file mode 100644 index 0000000000000..67cd635e2b954 --- /dev/null +++ b/src/Adapter/Discount/CommandHandler/AddFreeShippingDiscountHandler.php @@ -0,0 +1,52 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\Discount\CommandHandler; + +use PrestaShop\PrestaShop\Adapter\CartRule\CartRuleBuilder; +use PrestaShop\PrestaShop\Adapter\CartRule\Repository\CartRuleRepository; +use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler; +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddFreeShippingDiscountCommand; +use PrestaShop\PrestaShop\Core\Domain\Discount\CommandHandler\AddFreeShippingDiscountHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId; + +#[AsCommandHandler] +class AddFreeShippingDiscountHandler implements AddFreeShippingDiscountHandlerInterface +{ + public function __construct( + private readonly CartRuleRepository $cartRuleRepository, + private readonly CartRuleBuilder $cartRuleBuilder + ) { + } + + public function handle(AddFreeShippingDiscountCommand $command): DiscountId + { + $BuiltCartRule = $this->cartRuleBuilder->build($command); + $discount = $this->cartRuleRepository->add($BuiltCartRule); + + return new DiscountId((int) $discount->id); + } +} diff --git a/src/Adapter/Discount/QueryHandler/GetDiscountForEditingHandler.php b/src/Adapter/Discount/QueryHandler/GetDiscountForEditingHandler.php new file mode 100644 index 0000000000000..b690ab41a66d6 --- /dev/null +++ b/src/Adapter/Discount/QueryHandler/GetDiscountForEditingHandler.php @@ -0,0 +1,72 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\Discount\QueryHandler; + +use DateTimeImmutable; +use Exception; +use PrestaShop\PrestaShop\Adapter\CartRule\Repository\CartRuleRepository; +use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsQueryHandler; +use PrestaShop\PrestaShop\Core\Domain\CartRule\ValueObject\CartRuleId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\NoCustomerId; +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountForEditing; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler\GetDiscountForEditingHandlerInterface; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountForEditing; + +#[AsQueryHandler] +class GetDiscountForEditingHandler implements GetDiscountForEditingHandlerInterface +{ + public function __construct( + protected readonly CartRuleRepository $cartRuleRepository + ) { + } + + /** + * @throws Exception + */ + public function handle(GetDiscountForEditing $query): DiscountForEditing + { + $cartRuleId = new CartRuleId($query->discountId->getValue()); + $cartRule = $this->cartRuleRepository->get($cartRuleId); + + return new DiscountForEditing( + $query->discountId, + $cartRule->priority, + $cartRule->active, + new DateTimeImmutable($cartRule->date_from), + new DateTimeImmutable($cartRule->date_to), + $cartRule->quantity, + $cartRule->quantity_per_user, + $cartRule->description, + $cartRule->code, + (int) $cartRule->id_customer !== NoCustomerId::NO_CUSTOMER_ID_VALUE ? new CustomerId((int) $cartRule->id_customer) : new NoCustomerId(), + $cartRule->highlight, + $cartRule->partial_use, + $cartRule->type + ); + } +} diff --git a/src/Core/Domain/Discount/Command/AddDiscountCommand.php b/src/Core/Domain/Discount/Command/AddDiscountCommand.php new file mode 100644 index 0000000000000..d84ccf627cfad --- /dev/null +++ b/src/Core/Domain/Discount/Command/AddDiscountCommand.php @@ -0,0 +1,245 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Command; + +use DateTimeImmutable; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerId; +use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\DiscountConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountType; +use PrestaShop\PrestaShop\Core\Domain\Language\ValueObject\LanguageId; + +abstract class AddDiscountCommand +{ + private array $localizedNames; + private int $priority = 1; + private bool $active = true; + private ?DateTimeImmutable $validFrom; + private ?DateTimeImmutable $validTo; + private int $totalQuantity = 1; + private int $quantityPerUser = 1; + private string $description = ''; + private string $code = ''; + private ?CustomerId $customerId = null; + private bool $highlightInCart = false; + private bool $allowPartialUse = true; + private DiscountType $type; + + public function __construct( + array $localizedNames, + string $type, + ) { + $this->type = new DiscountType($type); + $this->setLocalizedNames($localizedNames); + } + + /** + * @param array $localizedNames + */ + private function setLocalizedNames(array $localizedNames): self + { + foreach ($localizedNames as $languageId => $name) { + $this->localizedNames[(new LanguageId($languageId))->getValue()] = $name; + } + + return $this; + } + + /** + * @return array + */ + public function getLocalizedNames(): array + { + return $this->localizedNames; + } + + public function isActive(): bool + { + return $this->active; + } + + public function setActive(bool $active): self + { + $this->active = $active; + + return $this; + } + + public function getValidFrom(): ?DateTimeImmutable + { + return $this->validFrom; + } + + public function getValidTo(): ?DateTimeImmutable + { + return $this->validTo; + } + + /** + * @throws DiscountConstraintException + */ + public function setValidityDateRange(DateTimeImmutable $from, DateTimeImmutable $to): self + { + $this->assertDateRangeIsValid($from, $to); + $this->validFrom = $from; + $this->validTo = $to; + + return $this; + } + + public function getPriority(): int + { + return $this->priority; + } + + public function getDiscountType(): DiscountType + { + return $this->type; + } + + /** + * @throws DiscountConstraintException + */ + public function setPriority(int $priority): self + { + if (0 >= $priority) { + throw new DiscountConstraintException( + sprintf('Invalid discount priority "%s". Must be a positive integer.', $priority), + DiscountConstraintException::INVALID_PRIORITY + ); + } + + $this->priority = $priority; + + return $this; + } + + public function getTotalQuantity(): int + { + return $this->totalQuantity; + } + + public function isHighlightInCart(): bool + { + return $this->highlightInCart; + } + + public function setHighlightInCart(bool $highlightInCart): void + { + $this->highlightInCart = $highlightInCart; + } + + /** + * @throws DiscountConstraintException + */ + public function setTotalQuantity(int $quantity): self + { + if (0 > $quantity) { + throw new DiscountConstraintException(sprintf('Quantity cannot be lower than zero, %d given', $quantity), DiscountConstraintException::INVALID_QUANTITY); + } + + $this->totalQuantity = $quantity; + + return $this; + } + + public function getQuantityPerUser(): int + { + return $this->quantityPerUser; + } + + /** + * @throws DiscountConstraintException + */ + public function setQuantityPerUser(int $quantity): self + { + if (0 > $quantity) { + throw new DiscountConstraintException(sprintf('Quantity per user cannot be lower than zero, %d given', $quantity), DiscountConstraintException::INVALID_QUANTITY_PER_USER); + } + + $this->quantityPerUser = $quantity; + + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getCode(): string + { + return $this->code; + } + + public function setCode(string $code): self + { + $this->code = $code; + + return $this; + } + + public function allowPartialUse(): bool + { + return $this->allowPartialUse; + } + + public function setAllowPartialUse(bool $allow): self + { + $this->allowPartialUse = $allow; + + return $this; + } + + public function getCustomerId(): ?CustomerId + { + return $this->customerId; + } + + public function setCustomerId(int $customerId): self + { + $this->customerId = new CustomerId($customerId); + + return $this; + } + + /** + * @throws DiscountConstraintException + */ + private function assertDateRangeIsValid(DateTimeImmutable $dateFrom, DateTimeImmutable $dateTo): void + { + if ($dateFrom > $dateTo) { + throw new DiscountConstraintException('Date from cannot be greater than date to.', DiscountConstraintException::DATE_FROM_GREATER_THAN_DATE_TO); + } + } +} diff --git a/src/Core/Domain/Discount/Command/AddFreeShippingDiscountCommand.php b/src/Core/Domain/Discount/Command/AddFreeShippingDiscountCommand.php new file mode 100644 index 0000000000000..1dcf41d034836 --- /dev/null +++ b/src/Core/Domain/Discount/Command/AddFreeShippingDiscountCommand.php @@ -0,0 +1,37 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Command; + +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountType; + +class AddFreeShippingDiscountCommand extends AddDiscountCommand +{ + public function __construct(array $localizedNames) + { + parent::__construct($localizedNames, DiscountType::FREE_SHIPPING); + } +} diff --git a/src/Core/Domain/Discount/CommandHandler/AddFreeShippingDiscountHandlerInterface.php b/src/Core/Domain/Discount/CommandHandler/AddFreeShippingDiscountHandlerInterface.php new file mode 100644 index 0000000000000..f40bf38cd5a77 --- /dev/null +++ b/src/Core/Domain/Discount/CommandHandler/AddFreeShippingDiscountHandlerInterface.php @@ -0,0 +1,35 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\CommandHandler; + +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddFreeShippingDiscountCommand; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId; + +interface AddFreeShippingDiscountHandlerInterface +{ + public function handle(AddFreeShippingDiscountCommand $command): DiscountId; +} diff --git a/src/Core/Domain/Discount/Exception/DiscountConstraintException.php b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php new file mode 100644 index 0000000000000..fabf2cd8c0606 --- /dev/null +++ b/src/Core/Domain/Discount/Exception/DiscountConstraintException.php @@ -0,0 +1,76 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Exception; + +class DiscountConstraintException extends DiscountException +{ + // @todo remove unused + public const MISSING_DISCOUNT_APPLICATION_PRODUCT = 1; + public const INVALID_DISCOUNT_APPLICATION_TYPE = 2; + public const INVALID_GIFT_PRODUCT_ATTRIBUTE = 3; + public const INVALID_PRIORITY = 4; + public const DATE_FROM_GREATER_THAN_DATE_TO = 5; + public const INVALID_QUANTITY = 6; + public const INVALID_QUANTITY_PER_USER = 7; + public const INVALID_GIFT_PRODUCT = 8; + public const MISSING_ACTION = 9; + public const INVALID_ID = 10; + public const INVALID_NAME = 11; + public const INVALID_STATUS = 12; + public const INVALID_CUSTOMER_ID = 13; + public const INVALID_DATE_FROM = 14; + public const INVALID_DATE_TO = 15; + public const INVALID_DESCRIPTION = 16; + public const INVALID_PARTIAL_USE = 17; + public const INVALID_CODE = 18; + public const INVALID_MINIMUM_AMOUNT = 19; + public const INVALID_MINIMUM_AMOUNT_TAX = 20; + public const INVALID_MINIMUM_AMOUNT_CURRENCY = 21; + public const INVALID_MINIMUM_AMOUNT_SHIPPING = 22; + public const INVALID_COUNTRY_RESTRICTION = 23; + public const INVALID_CARRIER_RESTRICTION = 24; + public const INVALID_GROUP_RESTRICTION = 25; + public const INVALID_CART_RULE_RESTRICTION = 26; + public const INVALID_PRODUCT_RESTRICTION = 27; + public const INVALID_SHOP_RESTRICTION = 28; + public const INVALID_FREE_SHIPPING = 29; + public const INVALID_REDUCTION_PERCENT = 30; + public const INVALID_REDUCTION_AMOUNT = 31; + public const INVALID_REDUCTION_TAX = 32; + public const INVALID_REDUCTION_CURRENCY = 33; + public const INVALID_REDUCTION_PRODUCT = 34; + public const INVALID_REDUCTION_EXCLUDE_SPECIAL = 35; + public const INVALID_HIGHLIGHT = 36; + public const INVALID_ACTIVE = 37; + public const NON_UNIQUE_CODE = 38; + public const INVALID_PRICE_DISCOUNT = 39; + public const INVALID_RESTRICTION_RULE_TYPE = 40; + public const INVALID_RESTRICTION_RULE_ID = 41; + public const EMPTY_RESTRICTION_RULE_IDS = 42; + public const EMPTY_RESTRICTION_RULES = 43; + public const INVALID_PROPERTY_FOR_TYPE = 44; +} diff --git a/src/Core/Domain/Discount/Exception/DiscountException.php b/src/Core/Domain/Discount/Exception/DiscountException.php new file mode 100644 index 0000000000000..d5985493e3516 --- /dev/null +++ b/src/Core/Domain/Discount/Exception/DiscountException.php @@ -0,0 +1,33 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Exception; + +use PrestaShop\PrestaShop\Core\Domain\Exception\DomainException; + +class DiscountException extends DomainException +{ +} diff --git a/src/Core/Domain/Discount/Exception/DiscountNotFoundException.php b/src/Core/Domain/Discount/Exception/DiscountNotFoundException.php new file mode 100644 index 0000000000000..1fe44123a476f --- /dev/null +++ b/src/Core/Domain/Discount/Exception/DiscountNotFoundException.php @@ -0,0 +1,31 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Exception; + +class DiscountNotFoundException extends DiscountException +{ +} diff --git a/src/Core/Domain/Discount/Query/GetDiscountForEditing.php b/src/Core/Domain/Discount/Query/GetDiscountForEditing.php new file mode 100644 index 0000000000000..e004a3f0ed7a2 --- /dev/null +++ b/src/Core/Domain/Discount/Query/GetDiscountForEditing.php @@ -0,0 +1,39 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\Query; + +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId; + +class GetDiscountForEditing +{ + public readonly DiscountId $discountId; + + public function __construct(int $discountId) + { + $this->discountId = new DiscountId($discountId); + } +} diff --git a/src/Core/Domain/Discount/QueryHandler/GetDiscountForEditingHandlerInterface.php b/src/Core/Domain/Discount/QueryHandler/GetDiscountForEditingHandlerInterface.php new file mode 100644 index 0000000000000..d88107b291976 --- /dev/null +++ b/src/Core/Domain/Discount/QueryHandler/GetDiscountForEditingHandlerInterface.php @@ -0,0 +1,35 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryHandler; + +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountForEditing; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountForEditing; + +interface GetDiscountForEditingHandlerInterface +{ + public function handle(GetDiscountForEditing $query): DiscountForEditing; +} diff --git a/src/Core/Domain/Discount/QueryResult/DiscountForEditing.php b/src/Core/Domain/Discount/QueryResult/DiscountForEditing.php new file mode 100644 index 0000000000000..0c58d820ec26d --- /dev/null +++ b/src/Core/Domain/Discount/QueryResult/DiscountForEditing.php @@ -0,0 +1,117 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult; + +use DateTimeImmutable; +use PrestaShop\PrestaShop\Core\Domain\Customer\ValueObject\CustomerIdInterface; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountType; + +class DiscountForEditing +{ + public function __construct( + private readonly DiscountId $id, + private readonly int $priority, + private readonly bool $active, + private readonly ?DateTimeImmutable $validFrom, + private readonly ?DateTimeImmutable $validTo, + private readonly int $totalQuantity, + private readonly int $quantityPerUser, + private readonly string $description, + private readonly string $code, + private readonly ?CustomerIdInterface $customerId, + private readonly bool $highlightInCart, + private readonly bool $allowPartialUse, + private readonly string $type, + ) { + } + + public function getDiscountId(): DiscountId + { + return $this->id; + } + + public function getPriority(): int + { + return $this->priority; + } + + public function isActive(): bool + { + return $this->active; + } + + public function getValidFrom(): ?DateTimeImmutable + { + return $this->validFrom; + } + + public function getValidTo(): ?DateTimeImmutable + { + return $this->validTo; + } + + public function getTotalQuantity(): int + { + return $this->totalQuantity; + } + + public function getQuantityPerUser(): int + { + return $this->quantityPerUser; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getCode(): string + { + return $this->code; + } + + public function getCustomerId(): ?CustomerIdInterface + { + return $this->customerId; + } + + public function isHighlightInCart(): bool + { + return $this->highlightInCart; + } + + public function isAllowPartialUse(): bool + { + return $this->allowPartialUse; + } + + public function getType(): DiscountType + { + return new DiscountType($this->type); + } +} diff --git a/src/Core/Domain/Discount/ValueObject/DiscountId.php b/src/Core/Domain/Discount/ValueObject/DiscountId.php new file mode 100644 index 0000000000000..2be480351c246 --- /dev/null +++ b/src/Core/Domain/Discount/ValueObject/DiscountId.php @@ -0,0 +1,57 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject; + +use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\DiscountConstraintException; + +class DiscountId +{ + private int $discountId; + + public function __construct(int $discountId) + { + $this->assertIsPositiveInt($discountId); + $this->discountId = $discountId; + } + + public function getValue(): int + { + return $this->discountId; + } + + /** + * @param int $value + * + * @throws DiscountConstraintException + */ + private function assertIsPositiveInt(int $value): void + { + if (0 > $value) { + throw new DiscountConstraintException(sprintf('Invalid discount id "%s".', $value), DiscountConstraintException::INVALID_ID); + } + } +} diff --git a/src/Core/Domain/Discount/ValueObject/DiscountType.php b/src/Core/Domain/Discount/ValueObject/DiscountType.php new file mode 100644 index 0000000000000..8186c62ac5149 --- /dev/null +++ b/src/Core/Domain/Discount/ValueObject/DiscountType.php @@ -0,0 +1,44 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject; + +class DiscountType +{ + public const FREE_SHIPPING = 'free_shipping'; + public const PERCENT_DISCOUNT = 'percent_discount'; + public const AMOUNT_DISCOUNT = 'amount_discount'; + public const FREE_GIFT = 'free_gift'; + + public function __construct(private readonly string $value) + { + } + + public function getValue(): string + { + return $this->value; + } +} diff --git a/src/Core/Domain/Discount/ValueObject/GiftedProduct.php b/src/Core/Domain/Discount/ValueObject/GiftedProduct.php new file mode 100644 index 0000000000000..36ba1831e2e78 --- /dev/null +++ b/src/Core/Domain/Discount/ValueObject/GiftedProduct.php @@ -0,0 +1,53 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject; + +class GiftedProduct +{ + private ?int $combinationId; + + public function __construct(private readonly int $productId) + { + } + + public function getProductId(): int + { + return $this->productId; + } + + public function getCombinationId(): ?int + { + return $this->combinationId; + } + + public function setCombinationId(?int $combinationId): self + { + $this->combinationId = $combinationId; + + return $this; + } +} diff --git a/src/Core/Domain/Discount/ValueObject/PercentageDiscount.php b/src/Core/Domain/Discount/ValueObject/PercentageDiscount.php new file mode 100644 index 0000000000000..e1a2c02f0f96c --- /dev/null +++ b/src/Core/Domain/Discount/ValueObject/PercentageDiscount.php @@ -0,0 +1,48 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject; + +use PrestaShop\Decimal\DecimalNumber; + +class PercentageDiscount +{ + public function __construct( + private readonly DecimalNumber $percentage, + private readonly bool $includeDiscountedProducts + ) { + } + + public function getPercentage(): DecimalNumber + { + return $this->percentage; + } + + public function applyToDiscountedProducts(): bool + { + return $this->includeDiscountedProducts; + } +} diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/cart_rule.yml b/src/PrestaShopBundle/Resources/config/services/adapter/cart_rule.yml index f32173a3fc13b..f8941b7720fa1 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/cart_rule.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/cart_rule.yml @@ -12,6 +12,11 @@ services: public: false autoconfigure: true + PrestaShop\PrestaShop\Adapter\CartRule\CartRuleBuilder: + autowire: true + public: false + autoconfigure: true + prestashop.adapter.cart_rule.query_handler.search_cart_rules_handler: class: PrestaShop\PrestaShop\Adapter\CartRule\QueryHandler\SearchCartRulesHandler autoconfigure: true diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml new file mode 100644 index 0000000000000..7a07cad44429a --- /dev/null +++ b/src/PrestaShopBundle/Resources/config/services/adapter/discount.yml @@ -0,0 +1,13 @@ +services: + _defaults: + public: true + + PrestaShop\PrestaShop\Adapter\Discount\CommandHandler\AddFreeShippingDiscountHandler: + autowire: true + public: false + autoconfigure: true + + PrestaShop\PrestaShop\Adapter\Discount\QueryHandler\GetDiscountForEditingHandler: + autowire: true + public: false + autoconfigure: true diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/AbstractDiscountFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/AbstractDiscountFeatureContext.php new file mode 100644 index 0000000000000..b65e9479bfb5f --- /dev/null +++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/AbstractDiscountFeatureContext.php @@ -0,0 +1,159 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace Tests\Integration\Behaviour\Features\Context\Domain\Discount; + +use DateTimeImmutable; +use Exception; +use PHPUnit\Framework\Assert; +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddDiscountCommand; +use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\DiscountConstraintException; +use PrestaShop\PrestaShop\Core\Domain\Discount\Query\GetDiscountForEditing; +use PrestaShop\PrestaShop\Core\Domain\Discount\QueryResult\DiscountForEditing; +use PrestaShop\PrestaShop\Core\Domain\Discount\ValueObject\DiscountId; +use PrestaShop\PrestaShop\Core\Util\DateTime\DateTime as DateTimeUtil; +use RuntimeException; +use Tests\Integration\Behaviour\Features\Context\Domain\AbstractDomainFeatureContext; +use Tests\Integration\Behaviour\Features\Context\Util\PrimitiveUtils; + +class AbstractDiscountFeatureContext extends AbstractDomainFeatureContext +{ + /** + * @throws DiscountConstraintException + * @throws Exception + */ + protected function createDiscountIfNotExist(string $cartRuleReference, array $data, AddDiscountCommand $command): void + { + if (isset($data['highlight'])) { + $command->setHighlightInCart(PrimitiveUtils::castStringBooleanIntoBoolean($data['highlight'])); + } + if (isset($data['allow_partial_use'])) { + $command->setAllowPartialUse(PrimitiveUtils::castStringBooleanIntoBoolean($data['allow_partial_use'])); + } + if (isset($data['priority'])) { + $command->setPriority((int) $data['priority']); + } + if (isset($data['active'])) { + $command->setActive(PrimitiveUtils::castStringBooleanIntoBoolean($data['active'])); + } + if (isset($data['valid_from'])) { + if (empty($data['valid_to'])) { + throw new RuntimeException('When setting cart rule range "valid_from" and "valid_to" must be provided'); + } + $command->setValidityDateRange( + new DateTimeImmutable($data['valid_from']), + new DateTimeImmutable($data['valid_to']), + ); + } + if (isset($data['total_quantity'])) { + $command->setTotalQuantity((int) $data['total_quantity']); + } + + if (isset($data['quantity_per_user'])) { + $command->setQuantityPerUser((int) $data['quantity_per_user']); + } + + $command->setDescription($data['description'] ?? ''); + if (!empty($data['code'])) { + $command->setCode($data['code']); + } + + /** @var DiscountId $discountId */ + $discountId = $this->getCommandBus()->handle($command); + $this->getSharedStorage()->set($cartRuleReference, $discountId->getValue()); + } + + protected function assertDiscountProperties(DiscountForEditing $discountForEditing, array $expectedData): void + { + if (isset($expectedData['description'])) { + Assert::assertSame($expectedData['description'], $discountForEditing->getDescription(), 'Unexpected description'); + } + if (isset($expectedData['highlight'])) { + Assert::assertSame( + PrimitiveUtils::castStringBooleanIntoBoolean($expectedData['highlight']), + $discountForEditing->isHighlightInCart(), + 'Unexpected highlight' + ); + } + if (isset($expectedData['allow_partial_use'])) { + Assert::assertSame( + PrimitiveUtils::castStringBooleanIntoBoolean($expectedData['allow_partial_use']), + $discountForEditing->isAllowPartialUse(), + 'Unexpected partial use' + ); + } + if (isset($expectedData['active'])) { + Assert::assertSame( + PrimitiveUtils::castStringBooleanIntoBoolean($expectedData['active']), + $discountForEditing->isActive(), + 'Unexpected active property' + ); + } + if (isset($expectedData['code'])) { + Assert::assertSame($expectedData['code'], $discountForEditing->getCode(), 'Unexpected code'); + } + if (isset($expectedData['customer'])) { + Assert::assertSame( + !empty($expectedData['customer']) ? $this->getSharedStorage()->get($expectedData['customer']) : 0, + $discountForEditing->getCustomerId()->getValue(), + 'Unexpected customer id' + ); + } + if (isset($expectedData['priority'])) { + Assert::assertSame((int) $expectedData['priority'], $discountForEditing->getPriority(), 'Unexpected priority'); + } + if (isset($expectedData['valid_from'])) { + Assert::assertEquals( + $expectedData['valid_from'], + $discountForEditing->getValidFrom()->format(DateTimeUtil::DEFAULT_DATETIME_FORMAT), + 'Unexpected valid_from' + ); + } + if (isset($expectedData['valid_to'])) { + Assert::assertEquals( + $expectedData['valid_to'], + $discountForEditing->getValidTo()->format(DateTimeUtil::DEFAULT_DATETIME_FORMAT), + 'Unexpected valid_to' + ); + } + if (isset($expectedData['total_quantity'])) { + Assert::assertSame((int) $expectedData['total_quantity'], $discountForEditing->getTotalQuantity(), 'Unexpected quantity'); + } + if (isset($expectedData['quantity_per_user'])) { + Assert::assertSame((int) $expectedData['quantity_per_user'], $discountForEditing->getQuantityPerUser(), 'Unexpected quantity_per_user'); + } + } + + protected function getDiscountForEditing(string $discountReference): DiscountForEditing + { + /** @var DiscountForEditing $discountForEditing */ + $discountForEditing = $this->getQueryBus()->handle( + new GetDiscountForEditing($this->getSharedStorage()->get($discountReference)) + ); + + return $discountForEditing; + } +} diff --git a/tests/Integration/Behaviour/Features/Context/Domain/Discount/AddDiscountFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/Discount/AddDiscountFeatureContext.php new file mode 100644 index 0000000000000..ae6353faf54f0 --- /dev/null +++ b/tests/Integration/Behaviour/Features/Context/Domain/Discount/AddDiscountFeatureContext.php @@ -0,0 +1,66 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace Tests\Integration\Behaviour\Features\Context\Domain\Discount; + +use Behat\Gherkin\Node\TableNode; +use PrestaShop\PrestaShop\Core\Domain\Discount\Command\AddFreeShippingDiscountCommand; +use PrestaShop\PrestaShop\Core\Domain\Discount\Exception\DiscountConstraintException; + +class AddDiscountFeatureContext extends AbstractDiscountFeatureContext +{ + /** + * @When I create a free shipping discount :discountReference with following properties: + * @When there is a free shipping discount :discountReference with following properties: + * + * @param string $discountReference + * @param TableNode $node + */ + public function createFreeShippingDiscountIfNotExists(string $discountReference, TableNode $node): void + { + $data = $this->localizeByRows($node); + try { + $command = new AddFreeShippingDiscountCommand($data['name']); + $this->createDiscountIfNotExist($discountReference, $data, $command); + } catch (DiscountConstraintException $e) { + $this->setLastException($e); + } + } + + /** + * @Then discount :discountReference should have the following properties: + * + * @param string $cartRuleReference + * @param TableNode $tableNode + */ + public function assertDiscount(string $cartRuleReference, TableNode $tableNode): void + { + $this->assertDiscountProperties( + $this->getDiscountForEditing($cartRuleReference), + $this->localizeByRows($tableNode) + ); + } +} diff --git a/tests/Integration/Behaviour/Features/Scenario/Discount/add_discount.feature b/tests/Integration/Behaviour/Features/Scenario/Discount/add_discount.feature new file mode 100644 index 0000000000000..8b0c05541af34 --- /dev/null +++ b/tests/Integration/Behaviour/Features/Scenario/Discount/add_discount.feature @@ -0,0 +1,43 @@ +# ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s discount --tags add-discount +@restore-all-tables-before-feature +@add-discount + +Feature: Add discount + PrestaShop allows BO users to create discounts + As a BO user + I must be able to create discounts + + Background: + Given shop "shop1" with name "test_shop" exists + Given there is a currency named "usd" with iso code "USD" and exchange rate of 0.92 + Given there is a currency named "chf" with iso code "CHF" and exchange rate of 1.25 + Given currency "usd" is the default one + And language with iso code "en" is the default one + + Scenario: Create a discount with free shipping + When I create a free shipping discount "discount_1" with following properties: + | name[en-US] | Promotion | + | description | Promotion for holidays | + | highlight | false | + | is_active | true | + | allow_partial_use | false | + | priority | 2 | + | valid_from | 2019-01-01 11:05:00 | + | valid_to | 2019-12-01 00:00:00 | + | total_quantity | 10 | + | quantity_per_user | 1 | + | free_shipping | true | + | code | PROMO_2019 | + And discount "discount_1" should have the following properties: + | name[en-US] | Promotion | + | description | Promotion for holidays | + | highlight | false | + | is_active | true | + | allow_partial_use | false | + | priority | 2 | + | valid_from | 2019-01-01 11:05:00 | + | valid_to | 2019-12-01 00:00:00 | + | total_quantity | 10 | + | quantity_per_user | 1 | + | free_shipping | true | + | code | PROMO_2019 | diff --git a/tests/Integration/Behaviour/behat.yml b/tests/Integration/Behaviour/behat.yml index 0eeb5cf2091bf..692be206b4675 100644 --- a/tests/Integration/Behaviour/behat.yml +++ b/tests/Integration/Behaviour/behat.yml @@ -256,37 +256,47 @@ default: - Tests\Integration\Behaviour\Features\Context\CommonFeatureContext - Tests\Integration\Behaviour\Features\Context\Domain\CmsPageFeatureContext cart_rule: - paths: - - "%paths.base%/Features/Scenario/CartRule" - contexts: - - Tests\Integration\Behaviour\Features\Context\AttributeFeatureContext - - Tests\Integration\Behaviour\Features\Context\AttributeGroupFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\Carrier\CarrierFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CustomerFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\Carrier\CarrierRangesFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\ZoneFeatureContext - - Tests\Integration\Behaviour\Features\Context\CarrierFeatureContext - - Tests\Integration\Behaviour\Features\Context\CommonFeatureContext - - Tests\Integration\Behaviour\Features\Context\CurrencyFeatureContext - - Tests\Integration\Behaviour\Features\Context\CustomerFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CategoryFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\AddCartRuleFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\CartRuleAssertionFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\DeleteCartRuleFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\EditCartRuleFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\SetCartRuleRestrictionsFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CountryFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\CustomerGroupFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\ManufacturerFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\Product\AddProductFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\CombinationListingFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\GenerateCombinationFeatureContext - - Tests\Integration\Behaviour\Features\Context\Domain\SupplierFeatureContext - - Tests\Integration\Behaviour\Features\Context\LanguageFeatureContext - - Tests\Integration\Behaviour\Features\Context\ShopFeatureContext - - Tests\Integration\Behaviour\Features\Transform\CurrencyTransformContext - - Tests\Integration\Behaviour\Features\Transform\SharedStorageTransformContext - - Tests\Integration\Behaviour\Features\Transform\StringToBoolTransformContext + paths: + - "%paths.base%/Features/Scenario/CartRule" + contexts: + - Tests\Integration\Behaviour\Features\Context\AttributeFeatureContext + - Tests\Integration\Behaviour\Features\Context\AttributeGroupFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Carrier\CarrierFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CustomerFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Carrier\CarrierRangesFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\ZoneFeatureContext + - Tests\Integration\Behaviour\Features\Context\CarrierFeatureContext + - Tests\Integration\Behaviour\Features\Context\CommonFeatureContext + - Tests\Integration\Behaviour\Features\Context\CurrencyFeatureContext + - Tests\Integration\Behaviour\Features\Context\CustomerFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CategoryFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\AddCartRuleFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\CartRuleAssertionFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\DeleteCartRuleFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\EditCartRuleFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CartRule\SetCartRuleRestrictionsFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CountryFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\CustomerGroupFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\ManufacturerFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Product\AddProductFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\CombinationListingFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\Product\Combination\GenerateCombinationFeatureContext + - Tests\Integration\Behaviour\Features\Context\Domain\SupplierFeatureContext + - Tests\Integration\Behaviour\Features\Context\LanguageFeatureContext + - Tests\Integration\Behaviour\Features\Context\ShopFeatureContext + - Tests\Integration\Behaviour\Features\Transform\CurrencyTransformContext + - Tests\Integration\Behaviour\Features\Transform\SharedStorageTransformContext + - Tests\Integration\Behaviour\Features\Transform\StringToBoolTransformContext + discount: + paths: + - "%paths.base%/Features/Scenario/Discount" + contexts: + - Tests\Integration\Behaviour\Features\Context\Domain\Discount\AddDiscountFeatureContext + - Tests\Integration\Behaviour\Features\Context\CommonFeatureContext + - Tests\Integration\Behaviour\Features\Context\CurrencyFeatureContext + - Tests\Integration\Behaviour\Features\Context\LanguageFeatureContext + - Tests\Integration\Behaviour\Features\Context\ShopFeatureContext + - Tests\Integration\Behaviour\Features\Transform\CurrencyTransformContext catalog_price_rule: paths: - "%paths.base%/Features/Scenario/CatalogPriceRule"