Implementing remaining promotion compatibility strategies:

  • Only with the selected promotions
  • Any promotion except the selected promotions

Issue fork commerce-3010862

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Oleksiy created an issue. See original summary.

oleksiy’s picture

Status: Active » Needs work
Issue tags: +Needs tests
StatusFileSize
new8.65 KB
kristiaanvandeneynde’s picture

Status: Needs work » Needs review
StatusFileSize
new9.12 KB

Here's a reroll

kristiaanvandeneynde’s picture

StatusFileSize
new2.74 KB
new11.62 KB

Okay, that went green so here is with updated tests.

Status: Needs review » Needs work

The last submitted patch, 4: commerce-3010862-4.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kristiaanvandeneynde’s picture

Issue tags: -Needs tests

Refinements up for discussion:

  1. Do not list the active promotion in the reference: It would be weird to be incompatible with oneself
  2. Should promotions that you are/aren't incompatible with be listed as dependencies?

Will fix the single failure shortly.

kristiaanvandeneynde’s picture

Status: Needs work » Needs review
StatusFileSize
new3.01 KB
new12.55 KB

This fixes the test failure. Could use some input on the 2 points raised in #6

kristiaanvandeneynde’s picture

StatusFileSize
new1.47 KB
new12.5 KB

The entity reference to the (in)compatible promotions should not be translatable... (IMO)

bojanz’s picture

Title: Port remaining strategies from Commerce Discount » Add missing promotion compatibility strategies

Updating title.

Lisa had a good comment in https://www.drupal.org/project/commerce/issues/2762997#comment-12342017:

One option that would be very helpful to me is allowing the compatibility to apply at the product/order item level instead of just to the order as a whole. Instead of just, "Not with any other promotions", you could also select "Not with any other order total promotions" or "Not with any other order item-level promotions".

Example: the store offers quantity discounts as follows:
* 5% off price for item quantity 5-9
* 10% off price for item quantity 10-19
* 15% off price for item quantity 20 or more

And then certain customers have standing discounts:
* John Smith receives 8% off any quantity of any item

And then suppose John Smith also has a coupon for 12% off product X.

He places an order:
2x product A
12x product B
24x product C
15x product X with coupon

With product/order item level compatibility, he could get 8% off product A, 10% off product B, 15% off product C, and 12% off product X. 4 different promotions applied but only one per order item.
---------------

Regarding compatibility settings in general, one concept I've seen in another system is the idea of a "terminating" condition. That provides a little more flexibility than the all-or-none option. A promotion that is flagged as terminating is compatible only with any lower weight conditions.

tonytheferg’s picture

Could this patch include an option to require the criteria of selected discounts rather than merely allowing compatibility? This is a concept I'm chewing on for complex promotions.

kristiaanvandeneynde’s picture

StatusFileSize
new11.98 KB

Reroll, haven't taken #9 into account yet.

gge’s picture

Hi,

Just tried to apply #11 to a Commerce 8.x-2.20 site. The patched applied correctly but when trying to run update.php got this:
Drupal\Core\Entity\Sql\SqlContentEntityStorageException: Table information not available for the 'compatibility_promotions' field. in Drupal\Core\Entity\Sql\DefaultTableMapping->getFieldTableName() (line 391 of core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php).

Thanks.

andriy khomych’s picture

Rerolled #11 patch for the last dev commerce version:

  • Fix install file code sniffer issue.
  • Change update hook number.
andriy khomych’s picture

Rerolled #11 patch for the 8.x-2.21.0 version:

  • Fix install file code sniffer issue.
  • Change update hook number.
artem rudnitskiy’s picture

Hi everyone, I have issue related to missing this new promotion compatibility strategies. I have promotion with 'Not with any other promotions' compatibility strategy. When I added coupon to the cart in the edit form, the first time it added correctly. But the next time when I'm trying to apply any non-coupon related changes, while saving the order, the promotion condition rejected the already added coupon due
to promotion compatibility strategy. Looks like the condition 'Only with the selected promotions' will solve the problem. So it will be grate to see this update in nearest module version if it possible.

flocondetoile’s picture

StatusFileSize
new10.95 KB

Patch #14 reroll against commerce 2.27. I change the number of the hook_update_N() after the last committed in -dev. I wonder how we can manage this kind of hook_update_N() in a patch, because if used on a project, then the next hook_update_N() could not be played because already done with this patch.

flocondetoile’s picture

StatusFileSize
new11.34 KB
new636 bytes

Little fix added about the promotion form (put the new field into the compatibilty details group).

dd_leo’s picture

StatusFileSize
new11.32 KB

Patch #17 is not relevant in Commerce version 2.28.
The problem mentioned in the #16 comment arose.
I changed the hook_update_N () number.

jsacksick’s picture

Any chance to get a patch that applies?

nathaniel’s picture

StatusFileSize
new11.51 KB

reroll

Status: Needs review » Needs work

The last submitted patch, 20: 3010862-20.patch, failed testing. View results

rgnyldz’s picture

Patch seems to apply without any issue bu nothing happens when I click on "Only with the selected promotions" or "Any promotion except the selected promotions"

I'm on drupal 10.0.1, commerce 2.32 and php8.1

Edit: Add log message

Warning: Undefined array key "#type" in Drupal\Core\Form\FormHelper::processStates() (line 211 of /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Form/FormHelper.php)
#0 /home/keratai2/public_html/xxxxxxxx.com/web/core/includes/bootstrap.inc(158): _drupal_error_handler_real()
#1 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Form/FormHelper.php(211): _drupal_error_handler()
#2 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(400): Drupal\Core\Form\FormHelper::processStates()
#3 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(446): Drupal\Core\Render\Renderer->doRender()
#4 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(204): Drupal\Core\Render\Renderer->doRender()
#5 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Template/TwigExtension.php(477): Drupal\Core\Render\Renderer->render()
#6 /home/keratai2/public_html/xxxxxxxx.com/web/sites/default/files/php/twig/63dd68a000571_commerce-promotion-form.h_x3KsDZaKdGiM4mqm3qjrVUvrW/HofUV6_DHYoSi-y2DzRbHbKgxYLndquBwufJzUssGTg.php(43): Drupal\Core\Template\TwigExtension->escapeFilter()
#7 /home/keratai2/public_html/xxxxxxxx.com/vendor/twig/twig/src/Template.php(394): __TwigTemplate_d465138c02272adc00ad0c96ff0d43bc->doDisplay()
#8 /home/keratai2/public_html/xxxxxxxx.com/vendor/twig/twig/src/Template.php(367): Twig\Template->displayWithErrorHandling()
#9 /home/keratai2/public_html/xxxxxxxx.com/vendor/twig/twig/src/Template.php(379): Twig\Template->display()
#10 /home/keratai2/public_html/xxxxxxxx.com/vendor/twig/twig/src/TemplateWrapper.php(40): Twig\Template->render()
#11 /home/keratai2/public_html/xxxxxxxx.com/web/core/themes/engines/twig/twig.engine(53): Twig\TemplateWrapper->render()
#12 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Theme/ThemeManager.php(372): twig_render_template()
#13 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(433): Drupal\Core\Theme\ThemeManager->render()
#14 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(204): Drupal\Core\Render\Renderer->doRender()
#15 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(238): Drupal\Core\Render\Renderer->render()
#16 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}()
#17 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(239): Drupal\Core\Render\Renderer->executeInRenderContext()
#18 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(128): Drupal\Core\Render\MainContent\HtmlRenderer->prepare()
#19 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php(90): Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse()
#20 [internal function]: Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray()
#21 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()
#22 /home/keratai2/public_html/xxxxxxxx.com/vendor/symfony/http-kernel/HttpKernel.php(168): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch()
#23 /home/keratai2/public_html/xxxxxxxx.com/vendor/symfony/http-kernel/HttpKernel.php(74): Symfony\Component\HttpKernel\HttpKernel->handleRaw()
#24 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\Component\HttpKernel\HttpKernel->handle()
#25 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
#26 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#27 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#28 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#29 /home/keratai2/public_html/xxxxxxxx.com/web/core/lib/Drupal/Core/DrupalKernel.php(681): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
#30 /home/keratai2/public_html/xxxxxxxx.com/web/index.php(19): Drupal\Core\DrupalKernel->handle()
#31 {main}
rgnyldz’s picture

My mistake, I did not run db update and cleared cache.

rgnyldz’s picture

I also have an edge case for you :)

I created a content type called Campaigns so the admins can create a page containing the promotion information and display it on a page with a reference field to promotions. So they have to only work on one page for all the info like promotion itself and images, descriptions etc.

But inside the node edit page the radio buttons inside the inline entity form(simple) do not work. I presume that the patch is specific for the promotion edit page ?

zaporylie’s picture

It seems to me like this patch won't work with coupons as the coupon validation constraint calls Promotion::applies() and that happens after the promotion preprocess removes all the promotion adjustments and before the processor adds them back.

Disclaimer - I didn't test that theory but working on something similar now so wanted to chime in.

bceyssens’s picture

patch #20 seems to work for us with latest 2.x dev version.

nicolas bouteille’s picture

Hello,

We have the same need than what was described on #9
For Black Friday, we want to offer -50% on all products, but also -70% on some categories.
Our clients need to be able to buy a product at -50% and another at -70% in the same cart / order.
It looks like as described in #9, in Commerce right now, when a promotion in incompatible with another one, it means they cannot be used in the same cart / order at all, even though they don't affect the same product. Which I think is too bad :/
I was actually surprised to discover this is the behavior so far.
Since Lisa's idea has been posted 6 years ago, then reposted on #9 4 years ago, I can only imagine this requires a lot of work and will not be available anytime soon… but can I suggest that this idea be at least mentioned in the description / roadmap of this issue?

Also, in the mean time, could you advise me on how you would implement my current need?
I was thinking about adding a custom plugin to be able to exclude specific products or taxonomies.
That way, I could create -50% on all products except one category, and another promo -70% on this specific category. Marking both promo compatible so that they can be both used at the same time but making sure on my end that they cannot affect one same product.

That makes me think that somebody has probably already created this kind of excluding plugin?

Thanks for your help.

Nicolas

nicolas bouteille’s picture

Ok just some follow up, here's how I managed to create a new condition to exclude some products (I will do the same for categories)
I just created a new plugin OrderItemExcludeProduct that extends OrderItemProduct and only overrides the evaluate() function to negate it:

public function evaluate(EntityInterface $entity) {
    return ! parent::evaluate($entity);
  }

I also took care of displaying a huge warning to make sure the person creating the promotion also adds another condition such as Specific product category AND selects 'Condition operator' : 'All conditions must pass'. If not, the consequence will be : the promotion will be applied on any product not excluded here, even if it does not match the other condition(s)...

By the way, the default value for Condition operator is 'Only one condition must pass' but shouldn't it say 'At least one condition must pass'?
I just tested this and it does not break if I have more than one condition passing...

derekw’s picture

Patch no longer applies to 3.0.2

flocondetoile’s picture

Version: 8.x-2.x-dev » 3.1.0

changed version

flocondetoile’s picture

Status: Needs work » Needs review
StatusFileSize
new11.39 KB

patch rerolled against 3.1.0 version

flocondetoile’s picture

StatusFileSize
new10.2 KB

For those who need the patch without the hook_update_N, as me who run this patch since 4 years (I implements the update_N in a custom module to not break other futur hook_update_N of the module), here it is.

PS: this patch is only for being added in some custom projects's composer.json

jsacksick’s picture

@flocondetoile: Can we get an MR? So we can see if the tests are passing/failing?

jsacksick’s picture

Version: 3.1.0 » 3.x-dev

I opened an MR with the patch from #31.

jsacksick’s picture

I'm curious, this patch was never RTBCED, @flocondetoile: Have you been encountering any issue in the projects where this has been applied?
I also think there are some use cases that won't work properly (due to how coupons are applied for example).

flocondetoile’s picture

I ran this patch since 4 years without issues. But I didn't have use case with coupons. I used it to make promotions incompatibles with others, without any coupons behavior associated with them.

derekw’s picture

I did have trouble using this patch when a coupon was applied and both were targeted at order items. When the coupon was applied no other discounts were applied even if they were applicable. Fortunately the coupon use was a rare case for us. Otherwise the patch worked.

Instead of using the Compatibility with other promotions feature I added custom conditions to evaluate order item product variation attributes for determining promotion applicability, and that was adequate to deconflict promotions.