diff --git a/features/promotion/managing_catalog_promotions/creating_catalog_promotion.feature b/features/promotion/managing_catalog_promotions/creating_catalog_promotion.feature index 33050bfff87..98c1d16b2d0 100644 --- a/features/promotion/managing_catalog_promotions/creating_catalog_promotion.feature +++ b/features/promotion/managing_catalog_promotions/creating_catalog_promotion.feature @@ -15,7 +15,7 @@ Feature: Creating a catalog promotion And it should have "winter_sale" code and "Winter sale" name And this catalog promotion should be usable - @api + @api @ui @javascript Scenario: Creating a catalog promotion Given the store has a "T-Shirt" configurable product And this product has "PHP T-Shirt" variant priced at "$20.00" diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingCatalogPromotionsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingCatalogPromotionsContext.php index 6a57db249be..43a9997fa98 100644 --- a/src/Sylius/Behat/Context/Api/Admin/ManagingCatalogPromotionsContext.php +++ b/src/Sylius/Behat/Context/Api/Admin/ManagingCatalogPromotionsContext.php @@ -19,11 +19,11 @@ use Sylius\Behat\Client\ResponseCheckerInterface; use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface; +use Sylius\Component\Core\Model\CatalogPromotionRuleInterface; use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ProductVariantInterface; use Sylius\Component\Promotion\Event\CatalogPromotionUpdated; use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface; -use Sylius\Component\Promotion\Model\CatalogPromotionRuleInterface; use Symfony\Component\Messenger\MessageBusInterface; use Webmozart\Assert\Assert; @@ -194,8 +194,6 @@ public function iBrowseCatalogPromotions(): void } /** - * @When I add the "contains variants" rule configured with :firstVariant and :secondVariant - * @When /^I add the "contains variants" rule configured with ("[^"]+" variant) and ("[^"]+" variant)$/ * @When /^it applies on variants ("[^"]+" variant) and ("[^"]+" variant)$/ */ public function iAddTheRuleConfiguredWithProductAnd(ProductVariantInterface $firstVariant, ProductVariantInterface $secondVariant): void diff --git a/src/Sylius/Behat/Context/Setup/CatalogPromotionContext.php b/src/Sylius/Behat/Context/Setup/CatalogPromotionContext.php index a60a5f2022f..e7e54d72ec3 100644 --- a/src/Sylius/Behat/Context/Setup/CatalogPromotionContext.php +++ b/src/Sylius/Behat/Context/Setup/CatalogPromotionContext.php @@ -18,11 +18,10 @@ use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Bundle\CoreBundle\Fixture\Factory\ExampleFactoryInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface; +use Sylius\Component\Core\Model\CatalogPromotionRuleInterface; use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\ProductVariantInterface; use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface; -use Sylius\Component\Promotion\Model\CatalogPromotionRule; -use Sylius\Component\Promotion\Model\CatalogPromotionRuleInterface; use Sylius\Component\Resource\Factory\FactoryInterface; final class CatalogPromotionContext implements Context @@ -92,7 +91,7 @@ public function itWillBeAppliedOnVariant(CatalogPromotionInterface $catalogPromo { /** @var CatalogPromotionRuleInterface $catalogPromotionRule */ $catalogPromotionRule = $this->catalogPromotionRuleFactory->createNew(); - $catalogPromotionRule->setType(CatalogPromotionRule::TYPE_FOR_VARIANTS); + $catalogPromotionRule->setType(CatalogPromotionRuleInterface::TYPE_FOR_VARIANTS); $catalogPromotionRule->setConfiguration([$variant->getCode()]); $catalogPromotion->addRule($catalogPromotionRule); diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingCatalogPromotionsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingCatalogPromotionsContext.php index 44e9cace397..e8ded6aaf67 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingCatalogPromotionsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingCatalogPromotionsContext.php @@ -18,7 +18,9 @@ use Sylius\Behat\Page\Admin\CatalogPromotion\CreatePageInterface; use Sylius\Behat\Page\Admin\CatalogPromotion\UpdatePageInterface; use Sylius\Behat\Page\Admin\Crud\IndexPageInterface; +use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface; +use Sylius\Component\Core\Model\ProductVariantInterface; use Webmozart\Assert\Assert; final class ManagingCatalogPromotionsContext implements Context @@ -31,16 +33,20 @@ final class ManagingCatalogPromotionsContext implements Context private FormElementInterface $formElement; + private SharedStorageInterface $sharedStorage; + public function __construct( IndexPageInterface $indexPage, CreatePageInterface $createPage, UpdatePageInterface $updatePage, - FormElementInterface $formElement + FormElementInterface $formElement, + SharedStorageInterface $sharedStorage ) { $this->indexPage = $indexPage; $this->createPage = $createPage; $this->updatePage = $updatePage; $this->formElement = $formElement; + $this->sharedStorage = $sharedStorage; } /** @@ -127,6 +133,28 @@ public function iMakeItUnavailableInChannel(string $channelName): void $this->formElement->uncheckChannel($channelName); } + /** + * @When /^it applies on variants ("[^"]+" variant) and ("[^"]+" variant)$/ + */ + public function itAppliesOnVariants(...$variants): void + { + $variantCodes = array_map(function(ProductVariantInterface $variant) { + return $variant->getCode(); + }, $variants); + + $this->formElement->addRule(); + $this->formElement->chooseLastRuleVariants($variantCodes); + } + + /** + * @When /^it gives the "([^"]+)%" percentage discount$/ + */ + public function catalogPromotionGivesDiscount(string $discount): void + { + $this->formElement->addAction(); + $this->formElement->specifyLastActionDiscount($discount); + } + /** * @When I add it */ @@ -194,6 +222,28 @@ public function itShouldHaveCodeAndName(string $code, string $name): void $this->indexPage->isSingleResourceOnPage(['name' => $name, 'code' => $code]), sprintf('Cannot find catalog promotions with code "%s" and name "%s" in the list', $code, $name) ); + + $actions = $this->indexPage->getActionsForResource(['name' => $name]); + $actions->clickLink('Edit'); + } + + /** + * @Then /^it should apply to ("[^"]+" variant) and ("[^"]+" variant)$/ + */ + public function itShouldHaveRule(ProductVariantInterface $firstVariant, ProductVariantInterface $secondVariant): void + { + $selectedVariants = $this->formElement->getLastRuleVariantCodes(); + + Assert::inArray($firstVariant->getCode(), $selectedVariants); + Assert::inArray($secondVariant->getCode(), $selectedVariants); + } + + /** + * @Then /^it should have "([^"]+)%" discount$/ + */ + public function itShouldHaveDiscount(string $amount): void + { + Assert::same($this->formElement->getLastActionDiscount(), $amount); } /** diff --git a/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElement.php b/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElement.php index ae417fcebb5..c72bb586b5c 100644 --- a/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElement.php +++ b/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElement.php @@ -13,7 +13,9 @@ namespace Sylius\Behat\Element\Admin\CatalogPromotion; +use Behat\Mink\Element\NodeElement; use FriendsOfBehat\PageObjectExtension\Element\Element; +use Webmozart\Assert\Assert; final class FormElement extends Element implements FormElementInterface { @@ -42,18 +44,83 @@ public function uncheckChannel(string $channelName): void $this->getDocument()->uncheckField($channelName); } + public function addRule(): void + { + $this->addCollectionElement('rules', 'add_rule_button'); + } + + public function addAction(): void + { + $this->addCollectionElement('actions', 'add_action_button'); + } + + public function chooseLastRuleVariants(array $variantCodes): void + { + $lastRule = $this->getElement('last_rule'); + + $lastRule->find('css', 'input[type="hidden"]')->setValue(implode(',', $variantCodes)); + } + + public function specifyLastActionDiscount(string $discount): void + { + $lastAction = $this->getElement('last_action'); + + $lastAction->find('css', 'input')->setValue($discount); + } + public function getFieldValueInLocale(string $field, string $localeCode): string { return $this->getElement($field, ['%localeCode%' => $localeCode])->getValue(); } + public function getLastRuleVariantCodes(): array + { + $lastRule = $this->getElement('last_rule'); + + return explode(',', $lastRule->find('css', 'input[type="hidden"]')->getValue()); + } + + public function getLastActionDiscount(): string + { + $lastAction = $this->getElement('last_action'); + + return $lastAction->find('css', 'input')->getValue(); + } + protected function getDefinedElements(): array { return array_merge(parent::getDefinedElements(), [ + 'actions' => '#actions', + 'add_action_button' => '#actions [data-form-collection="add"]', + 'add_rule_button' => '#rules [data-form-collection="add"]', 'channel' => '#sylius_catalog_promotion_code', 'description' => '#sylius_catalog_promotion_translations_%localeCode%_description', 'label' => '#sylius_catalog_promotion_translations_%localeCode%_label', + 'last_action' => '#actions [data-form-collection="item"]:last-child', + 'last_rule' => '#rules [data-form-collection="item"]:last-child', 'name' => '#sylius_catalog_promotion_name', + 'rules' => '#rules', ]); } + + private function addCollectionElement(string $collectionElement, string $buttonElement): void + { + $count = count($this->getCollectionItems($collectionElement)); + + $this->getElement($buttonElement)->click(); + + $this->getDocument()->waitFor(5, function () use ($count, $collectionElement) { + return $count + 1 === count($this->getCollectionItems($collectionElement)); + }); + } + + /** @return NodeElement[] */ + private function getCollectionItems(string $collection): array + { + $items = $this->getElement($collection)->findAll('css', 'div[data-form-collection="item"]'); + + Assert::isArray($items); + + return $items; + } } diff --git a/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElementInterface.php b/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElementInterface.php index d22b1322230..3b132a5dafa 100644 --- a/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElementInterface.php +++ b/src/Sylius/Behat/Element/Admin/CatalogPromotion/FormElementInterface.php @@ -25,5 +25,17 @@ public function checkChannel(string $channelName): void; public function uncheckChannel(string $channelName): void; + public function addRule(): void; + + public function addAction(): void; + + public function chooseLastRuleVariants(array $variantCodes): void; + + public function specifyLastActionDiscount(string $discount): void; + public function getFieldValueInLocale(string $field, string $localeCode): string; + + public function getLastRuleVariantCodes(): array; + + public function getLastActionDiscount(): string; } diff --git a/src/Sylius/Behat/Resources/config/services/contexts/ui.xml b/src/Sylius/Behat/Resources/config/services/contexts/ui.xml index d03d6384010..31178c273e1 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/ui.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/ui.xml @@ -50,6 +50,7 @@ + diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/routing/ajax/product_variant.yml b/src/Sylius/Bundle/AdminBundle/Resources/config/routing/ajax/product_variant.yml index d6c5c3e22c0..d896fc21fbc 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/config/routing/ajax/product_variant.yml +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/routing/ajax/product_variant.yml @@ -23,6 +23,21 @@ sylius_admin_ajax_product_variants_by_phrase: locale: expr:service('sylius.context.locale').getLocaleCode() productCode: $productCode +sylius_admin_ajax_all_product_variants_by_phrase: + path: /search-all + methods: [GET] + defaults: + _controller: sylius.controller.product_variant:indexAction + _format: json + _sylius: + serialization_groups: [Autocomplete] + permission: true + repository: + method: findByPhrase + arguments: + phrase: $phrase + locale: expr:service('sylius.context.locale').getLocaleCode() + sylius_admin_ajax_product_variants_by_codes: path: / methods: [GET] @@ -35,3 +50,16 @@ sylius_admin_ajax_product_variants_by_codes: repository: method: findByCodesAndProductCode arguments: [$code, $productCode] + +sylius_admin_ajax_all_product_variants_by_codes: + path: /all + methods: [GET] + defaults: + _controller: sylius.controller.product_variant:indexAction + _format: json + _sylius: + serialization_groups: [Autocomplete] + permission: true + repository: + method: findByCodes + arguments: [$code] diff --git a/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/_form.html.twig b/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/_form.html.twig index 20058375f7f..94a12c0ef1e 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/_form.html.twig +++ b/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/_form.html.twig @@ -1,11 +1,23 @@ {% from '@SyliusAdmin/Macro/translationForm.html.twig' import translationForm %} -
+{% form_theme form '@SyliusAdmin/CatalogPromotion/theme.html.twig' %} + +
{{ form_errors(form) }} -
- {{ form_row(form.code) }} - {{ form_row(form.name) }} - {{ form_row(form.channels) }} +
+
+ {{ form_row(form.code) }} + {{ form_row(form.name) }} + {{ form_row(form.channels) }} +
+
+ {{ form_row(form.rules) }} +
+
+ {{ form_row(form.actions) }} +
+
+
+ {{ translationForm(form.translations) }}
- {{ translationForm(form.translations) }}
diff --git a/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/theme.html.twig b/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/theme.html.twig new file mode 100644 index 00000000000..126165a77f1 --- /dev/null +++ b/src/Sylius/Bundle/AdminBundle/Resources/views/CatalogPromotion/theme.html.twig @@ -0,0 +1,6 @@ +{% extends '@SyliusAdmin/Form/theme.html.twig' %} + +{% block sylius_catalog_promotion_rule_widget %} + {{ form_row(form.type) }} + {{ form_row(form.configuration, {'remote_url': path('sylius_admin_ajax_all_product_variants_by_phrase'), 'remote_criteria_type': 'contains', 'remote_criteria_name': 'phrase', 'load_edit_url': path('sylius_admin_ajax_all_product_variants_by_codes')}) }} +{% endblock %} diff --git a/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductVariantsToCodesTransformer.php b/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductVariantsToCodesTransformer.php new file mode 100644 index 00000000000..cc2f8f555ff --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Form/DataTransformer/ProductVariantsToCodesTransformer.php @@ -0,0 +1,55 @@ +productVariantRepository = $productVariantRepository; + } + + /** @throws \InvalidArgumentException */ + public function transform($value): Collection + { + Assert::nullOrIsArray($value); + + if (empty($value)) { + return new ArrayCollection(); + } + + return new ArrayCollection($this->productVariantRepository->findBy(['code' => $value])); + } + + /** @throws \InvalidArgumentException */ + public function reverseTransform($productVariants): array + { + Assert::isInstanceOf($productVariants, Collection::class); + + return array_map(function (ProductVariantInterface $productVariant) { + return $productVariant->getCode(); + }, $productVariants->toArray()); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/Form/Extension/CatalogPromotionRuleTypeExtension.php b/src/Sylius/Bundle/CoreBundle/Form/Extension/CatalogPromotionRuleTypeExtension.php new file mode 100644 index 00000000000..75ef894cfb3 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Form/Extension/CatalogPromotionRuleTypeExtension.php @@ -0,0 +1,40 @@ +productVariantsToCodesTransformer = $productVariantsToCodesTransformer; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('configuration', ResourceAutocompleteChoiceType::class, [ + 'label' => 'sylius.ui.variants', + 'multiple' => true, + 'required' => false, + 'choice_name' => 'descriptor', + 'choice_value' => 'code', + 'resource' => 'sylius.product_variant', + ]); + + $builder->get('configuration')->addModelTransformer($this->productVariantsToCodesTransformer); + } + + public static function getExtendedTypes(): iterable + { + return [CatalogPromotionRuleType::class]; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/Provider/CatalogPromotionVariantsProvider.php b/src/Sylius/Bundle/CoreBundle/Provider/CatalogPromotionVariantsProvider.php index e5fa01cba95..b43f4b846bd 100644 --- a/src/Sylius/Bundle/CoreBundle/Provider/CatalogPromotionVariantsProvider.php +++ b/src/Sylius/Bundle/CoreBundle/Provider/CatalogPromotionVariantsProvider.php @@ -54,11 +54,11 @@ private function getVariantsProducts(array $configuration, array $variants): arr continue; } - if (!in_array($variant, $variants)) { - $variants[] = $variant; + if (!isset($variants[$variant->getCode()])) { + $variants[$variant->getCode()] = $variant; } } - return $variants; + return array_values($variants); } } diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_promotion.yml b/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_promotion.yml index f614c0216d6..f7e073960a5 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_promotion.yml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/app/sylius/sylius_promotion.yml @@ -6,6 +6,9 @@ sylius_promotion: catalog_promotion: classes: model: Sylius\Component\Core\Model\CatalogPromotion + catalog_promotion_rule: + classes: + model: Sylius\Component\Core\Model\CatalogPromotionRule promotion_subject: classes: model: "%sylius.model.order.class%" diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/CatalogPromotionRule.orm.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/CatalogPromotionRule.orm.xml new file mode 100644 index 00000000000..7400eb91a01 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/doctrine/model/CatalogPromotionRule.orm.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml index ad0458a0a16..7bbc9e2a1b5 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/form.xml @@ -78,6 +78,10 @@ + + + + @@ -190,6 +194,9 @@ + + + @@ -261,5 +268,14 @@ + + + %sylius.model.catalog_promotion_rule.class% + %sylius.form.type.catalog_promotion.validation_groups% + + Sylius\Component\Core\Model\CatalogPromotionRuleInterface::TYPE_FOR_VARIANTS + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml index 0a8a4a05b21..cdb5980ea68 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/CoreBundle/Resources/translations/messages.en.yml @@ -39,6 +39,8 @@ sylius: coupon: Coupon catalog_promotion: channels: Channels + rule: + for_variants: For variants checkout: addressing: different_billing_address: Use different address for billing? diff --git a/src/Sylius/Bundle/CoreBundle/Validator/Constraints/CatalogPromotionRulesValidator.php b/src/Sylius/Bundle/CoreBundle/Validator/Constraints/CatalogPromotionRulesValidator.php index d5fc8762d53..4419403121f 100644 --- a/src/Sylius/Bundle/CoreBundle/Validator/Constraints/CatalogPromotionRulesValidator.php +++ b/src/Sylius/Bundle/CoreBundle/Validator/Constraints/CatalogPromotionRulesValidator.php @@ -13,8 +13,8 @@ namespace Sylius\Bundle\CoreBundle\Validator\Constraints; +use Sylius\Component\Core\Model\CatalogPromotionRuleInterface; use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface; -use Sylius\Component\Promotion\Model\CatalogPromotionRuleInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Webmozart\Assert\Assert; diff --git a/src/Sylius/Bundle/CoreBundle/spec/Provider/CatalogPromotionVariantsProviderSpec.php b/src/Sylius/Bundle/CoreBundle/spec/Provider/CatalogPromotionVariantsProviderSpec.php index 5806149b692..f003967e869 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/Provider/CatalogPromotionVariantsProviderSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/Provider/CatalogPromotionVariantsProviderSpec.php @@ -52,6 +52,9 @@ function it_provides_products_with_configured_variants( $productVariantRepository->findOneBy(['code' => 'PHP_T_SHIRT_XS_BLACK'])->willReturn($secondVariant); $productVariantRepository->findOneBy(['code' => 'PHP_MUG'])->willReturn(null); + $firstVariant->getCode()->willReturn('PHP_T_SHIRT_XS_WHITE'); + $secondVariant->getCode()->willReturn('PHP_T_SHIRT_XS_BLACK'); + $this ->provideEligibleVariants($catalogPromotion) ->shouldReturn([$firstVariant, $secondVariant]) diff --git a/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/CatalogPromotionRulesValidatorSpec.php b/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/CatalogPromotionRulesValidatorSpec.php index f02bdcfdc41..c30e15bed4d 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/CatalogPromotionRulesValidatorSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/Validator/Constraints/CatalogPromotionRulesValidatorSpec.php @@ -15,13 +15,11 @@ use PhpSpec\ObjectBehavior; use Sylius\Bundle\CoreBundle\Validator\Constraints\CatalogPromotionRules; +use Sylius\Component\Core\Model\CatalogPromotionRuleInterface; use Sylius\Component\Core\Model\ProductVariantInterface; use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface; -use Sylius\Component\Promotion\Model\CatalogPromotionRuleInterface; -use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; final class CatalogPromotionRulesValidatorSpec extends ObjectBehavior { diff --git a/src/Sylius/Bundle/ProductBundle/Doctrine/ORM/ProductVariantRepository.php b/src/Sylius/Bundle/ProductBundle/Doctrine/ORM/ProductVariantRepository.php index f339dbfe675..31d341fe053 100644 --- a/src/Sylius/Bundle/ProductBundle/Doctrine/ORM/ProductVariantRepository.php +++ b/src/Sylius/Bundle/ProductBundle/Doctrine/ORM/ProductVariantRepository.php @@ -98,6 +98,16 @@ public function findByCodesAndProductCode(array $codes, string $productCode): ar ; } + public function findByCodes(array $codes): array + { + return $this->createQueryBuilder('o') + ->andWhere('o.code IN (:codes)') + ->setParameter('codes', $codes) + ->getQuery() + ->getResult() + ; + } + public function findOneByIdAndProductId($id, $productId): ?ProductVariantInterface { return $this->createQueryBuilder('o') @@ -129,4 +139,21 @@ public function findByPhraseAndProductCode(string $phrase, string $locale, strin ->getResult() ; } + + public function findByPhrase(string $phrase, string $locale): array + { + $expr = $this->getEntityManager()->getExpressionBuilder(); + + return $this->createQueryBuilder('o') + ->innerJoin('o.translations', 'translation', 'WITH', 'translation.locale = :locale') + ->andWhere($expr->orX( + 'translation.name LIKE :phrase', + 'o.code LIKE :phrase' + )) + ->setParameter('phrase', '%' . $phrase . '%') + ->setParameter('locale', $locale) + ->getQuery() + ->getResult() + ; + } } diff --git a/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogAction/PercentageDiscountActionConfigurationType.php b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogAction/PercentageDiscountActionConfigurationType.php new file mode 100644 index 00000000000..3ed92008a96 --- /dev/null +++ b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogAction/PercentageDiscountActionConfigurationType.php @@ -0,0 +1,26 @@ +add('amount', PercentType::class, [ + 'label' => 'sylius.ui.amount', + ]) + ; + } + + public function getBlockPrefix(): string + { + return 'sylius_catalog_promotion_action_percentage_discount_configuration'; + } +} diff --git a/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionActionType.php b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionActionType.php new file mode 100644 index 00000000000..db7a9baf59d --- /dev/null +++ b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionActionType.php @@ -0,0 +1,41 @@ +actionTypes = $actionTypes; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('type', ChoiceType::class, [ + 'label' => 'sylius.ui.type', + 'choices' => $this->actionTypes, + ]) + ->add('configuration', PercentageDiscountActionConfigurationType::class, [ + 'label' => false, + ]) + ; + } + + public function getBlockPrefix(): string + { + return 'sylius_catalog_promotion_action'; + } + +} diff --git a/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionRuleType.php b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionRuleType.php new file mode 100644 index 00000000000..04ff25d333b --- /dev/null +++ b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionRuleType.php @@ -0,0 +1,37 @@ +ruleTypes = $ruleTypes; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('type', ChoiceType::class, [ + 'label' => 'sylius.ui.type', + 'choices' => $this->ruleTypes, + ]) + ; + } + + public function getBlockPrefix(): string + { + return 'sylius_catalog_promotion_rule'; + } + +} diff --git a/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionType.php b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionType.php index c5b6d35a1d5..8664e6e44c7 100644 --- a/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionType.php +++ b/src/Sylius/Bundle/PromotionBundle/Form/Type/CatalogPromotionType.php @@ -16,6 +16,7 @@ use Sylius\Bundle\ResourceBundle\Form\EventSubscriber\AddCodeFormSubscriber; use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType; use Sylius\Bundle\ResourceBundle\Form\Type\ResourceTranslationsType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; @@ -44,6 +45,20 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'entry_type' => $this->catalogPromotionTranslationType, 'label' => 'sylius.form.catalog_promotion.translations', ]) + ->add('rules', CollectionType::class, [ + 'label' => 'sylius.ui.rules', + 'entry_type' => CatalogPromotionRuleType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + ]) + ->add('actions', CollectionType::class, [ + 'label' => 'sylius.ui.actions', + 'entry_type' => CatalogPromotionActionType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + ]) ; } diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/config/services/forms.xml b/src/Sylius/Bundle/PromotionBundle/Resources/config/services/forms.xml index 3152012b9af..596e252fa39 100644 --- a/src/Sylius/Bundle/PromotionBundle/Resources/config/services/forms.xml +++ b/src/Sylius/Bundle/PromotionBundle/Resources/config/services/forms.xml @@ -43,6 +43,21 @@ + + %sylius.model.catalog_promotion_rule.class% + %sylius.form.type.catalog_promotion.validation_groups% + + + + + %sylius.model.catalog_promotion_action.class% + %sylius.form.type.catalog_promotion.validation_groups% + + Sylius\Component\Promotion\Model\CatalogPromotionActionInterface::TYPE_PERCENTAGE_DISCOUNT + + + + %sylius.model.catalog_promotion_translation.class% %sylius.form.type.catalog_promotion_translation.validation_groups% diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/PromotionBundle/Resources/translations/messages.en.yml index cd5086ee140..8bea551237f 100644 --- a/src/Sylius/Bundle/PromotionBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/PromotionBundle/Resources/translations/messages.en.yml @@ -3,6 +3,8 @@ sylius: form: catalog_promotion: + action: + percentage_discount: Percentage discount description: Description label: Label name: Name diff --git a/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml b/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml index bb97d53013e..cc9da012ab5 100644 --- a/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml +++ b/src/Sylius/Bundle/UiBundle/Resources/translations/messages.en.yml @@ -256,6 +256,7 @@ sylius: edit_addresses: 'Edit addresses' edit_admin_user: 'Edit administrator' edit_association_type: 'Edit Association type' + edit_catalog_promotion: 'Edit catalog promotion' edit_channel: 'Edit channel' edit_country: 'Edit country' edit_coupon: 'Edit coupon' diff --git a/src/Sylius/Component/Core/Model/CatalogPromotionInterface.php b/src/Sylius/Component/Core/Model/CatalogPromotionInterface.php index 27d0f503739..86f2ea58120 100644 --- a/src/Sylius/Component/Core/Model/CatalogPromotionInterface.php +++ b/src/Sylius/Component/Core/Model/CatalogPromotionInterface.php @@ -18,5 +18,4 @@ interface CatalogPromotionInterface extends BaseCatalogPromotionInterface, ChannelsAwareInterface { - public const TYPE_CONTAINS_VARIANTS = 'contains_variants'; } diff --git a/src/Sylius/Component/Core/Model/CatalogPromotionRule.php b/src/Sylius/Component/Core/Model/CatalogPromotionRule.php new file mode 100644 index 00000000000..4bf598994fe --- /dev/null +++ b/src/Sylius/Component/Core/Model/CatalogPromotionRule.php @@ -0,0 +1,20 @@ +shouldImplement(CatalogPromotionRuleInterface::class); + } + + function it_extends_base_catalog_promotion_rule(): void + { + $this->shouldHaveType(CatalogPromotionRule::class); + } +} diff --git a/src/Sylius/Component/Product/Repository/ProductVariantRepositoryInterface.php b/src/Sylius/Component/Product/Repository/ProductVariantRepositoryInterface.php index 42ffc4061fc..db53795d0d6 100644 --- a/src/Sylius/Component/Product/Repository/ProductVariantRepositoryInterface.php +++ b/src/Sylius/Component/Product/Repository/ProductVariantRepositoryInterface.php @@ -43,10 +43,22 @@ public function findOneByCodeAndProductCode(string $code, string $productCode): */ public function findByCodesAndProductCode(array $codes, string $productCode): array; + /** + * @param array|string[] $codes + * + * @return array|ProductVariantInterface[] + */ + public function findByCodes(array $codes): array; + public function findOneByIdAndProductId($id, $productId): ?ProductVariantInterface; /** * @return array|ProductVariantInterface[] */ public function findByPhraseAndProductCode(string $phrase, string $locale, string $productCode): array; + + /** + * @return array|ProductVariantInterface[] + */ + public function findByPhrase(string $phrase, string $locale): array; } diff --git a/src/Sylius/Component/Promotion/Model/CatalogPromotionRuleInterface.php b/src/Sylius/Component/Promotion/Model/CatalogPromotionRuleInterface.php index 971f0ce05cf..4cd5b4f9662 100644 --- a/src/Sylius/Component/Promotion/Model/CatalogPromotionRuleInterface.php +++ b/src/Sylius/Component/Promotion/Model/CatalogPromotionRuleInterface.php @@ -17,8 +17,6 @@ interface CatalogPromotionRuleInterface extends ResourceInterface { - public const TYPE_FOR_VARIANTS = 'for_variants'; - public function setType(?string $type): void; public function getType(): ?string; diff --git a/tests/Api/Admin/CatalogPromotionsTest.php b/tests/Api/Admin/CatalogPromotionsTest.php index e3dc6f6fa54..794430cee55 100644 --- a/tests/Api/Admin/CatalogPromotionsTest.php +++ b/tests/Api/Admin/CatalogPromotionsTest.php @@ -13,8 +13,8 @@ namespace Sylius\Tests\Api\Admin; -use Sylius\Component\Promotion\Model\CatalogPromotionRuleInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface; +use Sylius\Component\Core\Model\CatalogPromotionRuleInterface; use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface; use Sylius\Tests\Api\JsonApiTestCase; use Sylius\Tests\Api\Utils\AdminUserLoginTrait;