#2842924: Add a product condition added a condition that allows limiting by product.
Ideally we'd also be able to limit by variation after selecting a product ("Applies to all variations" / selected specific variations).
Space was left in $this->configuration['product'] to allow for that.

The main problem is figuring out the UI.
Matt wanted to be able to select individual attributes if the variation type supports them, but that can result in a potentially huge form.
I'm more inclined to just go with an autocomplete shown when the "Applies to all variations" is unchecked.
Alternatively it might make sense to think about a UI that opens a modal for selecting a product & variations, that updates the settings form when closed.

CommentFileSizeAuthor
#44 commerce-condition_plugin_purchased_entities-2887061-44-do-not-test.patch22.89 KBELC
#44 commerce-add_default_configuration_2887061-44.patch1.48 KBELC
#38 interdiff-2887061-36-38.txt1.3 KBmglaman
#38 introduce_a_conditio-2887061-38.patch21.1 KBmglaman
#36 introduce_a_conditio-2887061-36.patch21.07 KBmglaman
#36 interdiff-2887061-35-36.txt9.25 KBmglaman
#35 interdiff-2887061-32-35.txt11.49 KBmglaman
#35 introduce_a_conditio-2887061-35.patch21.96 KBmglaman
#32 interdiff-2887061-26-32.txt24.13 KBmglaman
#32 introduce_an_order_i-2887061-32.patch23.96 KBmglaman
#32 Screen Shot 2020-07-27 at 2.50.34 PM.png59.97 KBmglaman
#26 2887061-26.patch16.73 KBjsacksick
#26 interdiff_24-26.txt2.84 KBjsacksick
#24 interdiff_20-23.txt5.99 KBjsacksick
#24 2887061-23.patch16.23 KBjsacksick
#21 interdiff_17-20.txt4.42 KBjsacksick
#20 interdiff_17-20.patch4.42 KBjsacksick
#20 2887061-20.patch16.79 KBjsacksick
#17 2887061-17.patch16.93 KBjsacksick
#17 interdiff_16-17.txt1.72 KBjsacksick
#16 interdiff_12-16.txt12.95 KBjsacksick
#16 2887061-16.patch16.93 KBjsacksick
#13 select-variations-12.png63.42 KBbojanz
#13 select-variations-multiple.png150.72 KBbojanz
#12 2887061-12.patch13.49 KBlawxen
#5 promotion-order_item_product-variation-2887061-5.patch6.39 KBGoZ
#3 2887061.txt4.4 KBchrisrockwell
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bojanz created an issue. See original summary.

Sinan Erdem’s picture

Hi Bojan,

Just an idea; the condition creation part of Drupal 7 Rules was very easy to use. You would:
1. Add a new condition
2. Select the field as tokens like: node:author:email
3. Select the values either as fixed values or as tokens.

Do you think it is applicable here? Like:

1. Add condition
2. Select product attribute
3. Select desired values

chrisrockwell’s picture

FileSize
4.4 KB

I don't think this is the direction that this issue is going to do but I'm attaching the plugin I'm using for now, I need something quick for allowing selecting variations. It's a copy of OrderItemProduct adapted for variations. A clear shortcoming is that someone could select variations, and then restrict to a product that doesn't have those variations and the coupon would never apply. Maybe this can help someone in the meantime.

chrisrockwell’s picture

+++ b/src/Plugin/Commerce/Condition/OrderItemVariation.php
@@ -0,0 +1,129 @@
+    $variation_ids = array_column($this->configuration['variations'], 'variation_id');

Should be target_id if you use this :) Argh, there are some other issues in the evaluate method - just ping me if you want the updated stuff :D

GoZ’s picture

Here is another way to limit by variations. Once products are selected in autocomplete field, variations are loaded as pre-checked checboxes field.
Now product limitation only check variations, no more products.

GoZ’s picture

Just figure out this patch should add a hook_update or deal with old product way to evaluate conditions

Status: Needs review » Needs work
GoZ’s picture

Of course, some tests are also needed, so i think we should update this to make old behavior works without hook_update (previous tests should pass) + add new tests for variations

lawxen’s picture

OrderItemProduct.php has been changed on 21 June 2018 in issue Rename conditions to remove the "Limit by" prefix,
#5 can't be applied to latest commerce module, so I can't test the effect of it.

lawxen’s picture

Issue tags: +ny-bogo
lawxen’s picture

Matt wanted to be able to select individual attributes if the variation type supports them, but that can result in a potentially huge form.
I'm more inclined to just go with an autocomplete shown when the "Applies to all variations" is unchecked.

All of this is too complicated.

Consider that every condition is "or", so we can just add another condition OrderItemVariaiton/OrderVariation, more simple more better.
Just like #3 did.

lawxen’s picture

Status: Needs work » Needs review
FileSize
13.49 KB

Add "OrderItemVariatoin,OrderVariation,OrderItemVariationTest,OrderVariationTest"
Just some copy of "OrderItemProduct,OrderProduct,OrderItemProductTest,OrderProductTest"

bojanz’s picture

This issue now has several competing approaches.

The problem with #5 is that products have a potentially high number of variations, and this patch code outputs a checkbox for each one.
5 products with 50 variations each result in 250 checkboxes added to the page. Even 50 checkboxes would break the UI. Checkboxes also make it impossible to differentiate between "All variations are allowed" and "All currently created variations are allowed" (when a new variation is created, is it then allowed automatically or not?)

I tried to convert the checkboxes into a multiple select box, but multiple select boxes suck (select-variations-multiple.png), they are a very non-obvious UI element (having to hold shift to select, etc). One more example of how not having Select2 hurts us in Drupal.

#12 adds a separate condition (select-variations-12.png). This highlights the problem from #2985571: Emphasize that conditions configured on the "Offer" level add up instead of restrict, it becomes less obvious that the conditions are additive, my colleague's hunch was that he should specify both the product (in "Specific product") and the product variations (in "Specific product variations"). This would have still worked (due to the ORing), but it shows that the UI is not fully logical. Might still be our best bet, though.

Cheviot’s picture

Hope I'm not suggestion something impossible given the existing structure in the promotion menu.

I have great difficulties to figure out how to use the existing taxonomies used within the existing variations. It seems to me impossible in the current structure. I haven't found any use for 'categories'. In the current concept the taxonomies seems to brought into focus for use on every level irregardless if they are associated to product or variations. But why do I see no result in 'categories'?

To have a more elegant process, should it not be a nested query process to limit the query size?
And how about selecting stores?

Would this concept still be possible? I show some ideas.
First select store X (click and query follows and finds nested items - if null 'no product type found')
select product type X (same - if null 'no product variations found')
select the available (same - if null 'no taxonomies found')
select the available taxonomy
select product type Y
select the available variation
select the available taxonomy
First select store Y
select product type Z (click and query follows, multiple nested items found - if null 'no product type found', taxonomy not mentioned)
select a product type related taxonomy
select the available variation
select the available taxonomy

And separately select AND / OR discounted quantity.
And separately select AND / OR value totals

That way even a massive list can be reduce to show only what is selected / exists.
But it deviates a lot from the current structure. It's just an idea which may improve it a bit.

jsacksick’s picture

Status: Needs review » Needs work

I think the approach in #12 is the right one, I closed #2971381: Add a product SKU condition in favor of this.

The patch needs minor tweaks though. We store the UUIDS for the "order_item_product" condition, and use the entity uuid mapper. We should do the same. Otherwise the patch looks good, especially now that #3083123: Product variation autocomplete should allow matching by SKU is in (since that allows searching by title + SKU).

The only potential problem I see is that I'm wondering if customers wouldn't expect the search to be limited to products selected (If "applies to selected products is also checked and products are selected).

So wrapping it up.

jsacksick’s picture

jsacksick’s picture

FileSize
1.72 KB
16.93 KB

Comment fix :p.

jsacksick’s picture

Title: Expand the order_item_product condition to support also limiting by variation » Introduce an order_item_variation condition plugin
mglaman’s picture

  1. +++ b/modules/product/src/Plugin/Commerce/Condition/OrderItemVariation.php
    @@ -0,0 +1,80 @@
    +    $variation_ids = $this->getVariationIds();
    +
    +    return in_array($purchased_entity->id(), $variation_ids);
    

    Couldn't we just get the UUID from the product variation and check from the configuration instead of querying for the mapped identifier?

  2. +++ b/modules/product/src/Plugin/Commerce/Condition/OrderVariation.php
    @@ -0,0 +1,85 @@
    +      if (in_array($purchased_entity->id(), $variation_ids)) {
    +        return TRUE;
    +      }
    

    Again, couldn't we just use the uuid method to check, without having to map the UUIDs?

  3. +++ b/modules/product/src/Plugin/Commerce/Condition/VariationTrait.php
    @@ -0,0 +1,88 @@
    +trait VariationTrait {
    

    +1 on trait, helps folks build custom conditions.

jsacksick’s picture

jsacksick’s picture

FileSize
4.42 KB
mglaman’s picture

+++ b/modules/product/src/Plugin/Commerce/Condition/OrderItemVariation.php
@@ -0,0 +1,79 @@
+    return in_array($purchased_entity->uuid(), $this->getVariationUuids(), TRUE);

+++ b/modules/product/src/Plugin/Commerce/Condition/OrderVariation.php
@@ -0,0 +1,85 @@
+      if (in_array($purchased_entity->uuid(), $variation_uuids, TRUE)) {
+        return TRUE;
+      }

+++ b/modules/product/src/Plugin/Commerce/Condition/VariationTrait.php
@@ -0,0 +1,87 @@
+  protected function getVariationUuids() {
+    return array_column($this->configuration['variations'], 'variation');
+  }

At this point, do we get getVariationUuids or just an isValid that gets the UUIDs and does the in_array comparison?

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

jsacksick’s picture

FileSize
16.23 KB
5.99 KB

I renamed getVariationUuids() to isUuidValid() (could not come up with a better name.

The problem with naming the method this way is that it could potentially conflict with someone else's code (since it's a Trait).

@mglaman: better naming in mind?

mglaman’s picture

+++ b/modules/product/src/Plugin/Commerce/Condition/OrderItemVariation.php
@@ -73,7 +73,7 @@ class OrderItemVariation extends ConditionBase implements ContainerFactoryPlugin
-    return in_array($purchased_entity->uuid(), $this->getVariationUuids(), TRUE);
+    return $this->isUuidValid($purchased_entity->uuid());

+++ b/modules/product/src/Plugin/Commerce/Condition/OrderVariation.php
@@ -67,14 +67,13 @@ class OrderVariation extends ConditionBase implements ContainerFactoryPluginInte
-      if (in_array($purchased_entity->uuid(), $variation_uuids, TRUE)) {
+      if ($this->isUuidValid($purchased_entity->uuid())) {

Looks good. 🤣another thing: should we just have "isValid" and pass the purchased entity?

jsacksick’s picture

FileSize
2.84 KB
16.73 KB

We actually still need getVariationIds() that is used to set the default value of the field.

jsacksick’s picture

@mglaman: I just realized, what about other purchasable entity types? Should we provide support for that? Using deriver perhaps? technically, the only main difference is the entity reference field that needs to target the right entity type. The rest should just work?

mglaman’s picture

+++ b/modules/product/config/schema/commerce_product.schema.yml
@@ -42,6 +42,22 @@ commerce.commerce_condition.plugin.order_product_type:
+commerce.commerce_condition.plugin.order_variation:
...
+    variations:
...
+      label: 'Variations'
...
+          variation:
...
+            label: 'Product variation'
...
+commerce.commerce_condition.plugin.order_item_variation:
+  type: commerce.commerce_condition.plugin.order_variation

So we'd basically call these "purchased_entity"

We label them as "Specific @label" where @lavel is the plural label from the entity type definition.

It's a lot of tedious work (not hard, but lot of changes.) But it technically is the "right" way since we have purchasable entity from the order item not just variations.

jsacksick’s picture

It's a lot of tedious work (not hard, but lot of changes.) But it technically is the "right" way since we have purchasable entity from the order item not just variations.

I agree, I think we need to do this the right way and be consistent with the rest of Commerce which technically supports different purchasable entity types.

jsacksick’s picture

Status: Needs review » Needs work

So setting this back to needs work.

mglaman’s picture

Assigned: Unassigned » mglaman

Wrapping this up today

mglaman’s picture

Okay, here is it converted to a purchasable entity plugin with a derived, in the Order namespace. The Product module performs an alter to "fix" the category.


  1. +++ b/modules/order/config/schema/commerce_order.schema.yml
    @@ -68,6 +68,22 @@ commerce.commerce_condition.plugin.order_total_price:
    +      sequence:
    +        type: mapping
    +        mapping:
    +          entity:
    +            type: uuid
    +            label: 'Purchasable entity'
    

    I don't know why we opted for a mapping here instead of just a sequence? We use a sequence for the order_store plugin and removes the need for array_column.

  2. +++ b/modules/order/src/Plugin/Commerce/Condition/OrderItemPurchasedEntity.php
    @@ -0,0 +1,86 @@
    +  use PurchasedEntityTrait;
    
    +++ b/modules/order/src/Plugin/Commerce/Condition/OrderPurchasedEntity.php
    @@ -0,0 +1,92 @@
    +  use PurchasedEntityTrait;
    
    +++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityTrait.php
    @@ -0,0 +1,114 @@
    +trait PurchasedEntityTrait {
    

    The trait feels kind of weird. I'd rather a shared base class. One that just sets the configuration form up, still.

  3. +++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityTrait.php
    @@ -0,0 +1,114 @@
    +    $entity_uuids = array_column($this->configuration['entities'], 'entity');
    +    return $this->entityUuidMapper->mapToIds($this->getPurchasableEntityType(), $entity_uuids);
    

    Moving the config from a mapping to sequence simplifies this. Probably removes the need for the method, even.

  4. +++ b/modules/product/commerce_product.module
    @@ -345,3 +346,12 @@ function commerce_product_config_schema_info_alter(&$definitions) {
    +
    +function commerce_product_commerce_condition_info_alter(array &$definitions) {
    

    Missing doc block


PHPCS violations

FILE: ...modules/contrib/commerce/modules/product/commerce_product.module
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 350 | ERROR | [x] Missing function doc comment
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
FILE: .../src/Unit/Plugin/Commerce/Condition/OrderPurchasedEntityTest.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 10 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------


FILE: ...ernel/Plugin/Commerce/Condition/PurchasedEntityConditionTest.php
----------------------------------------------------------------------
FOUND 2 ERRORS AND 1 WARNING AFFECTING 3 LINES
----------------------------------------------------------------------
  5 | WARNING | [x] Unused use statement
 16 | ERROR   | [ ] Parameter $expected_label is not described in
    |         |     comment
 34 | ERROR   | [x] Missing function doc comment
----------------------------------------------------------------------
PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

mglaman’s picture

Title: Introduce an order_item_variation condition plugin » Introduce a condition plugin for purchased entities
jsacksick’s picture

I don't know why we opted for a mapping here instead of just a sequence? We use a sequence for the order_store plugin and removes the need for array_column.

I wondered the same while working on the order_item_variation condition plugin, and I did this because it matched what was done for products, but I see no reason to not switch to a simple sequence instead.

Regarding the trait, feel free to try with a different approach (but once again, that is the pattern that was used for products, and product types.

mglaman’s picture

Here is a reroll dropping the trait for a base class, it feels a bit cleaner.

mglaman’s picture

Here is another go, which refactors the schema storage and evaluation method.

jsacksick’s picture

This looks good, few nitpicks.

  1. +++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionDeriver.php
    @@ -0,0 +1,67 @@
    +   * The entity manager.
    

    The entity type manager :).

  2. +++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionDeriver.php
    @@ -0,0 +1,67 @@
    +   * Constructs a new PackageTypeDeriver object.
    

    This should be PurchasedEntityConditionDeriver.

  3. +++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionDeriver.php
    @@ -0,0 +1,67 @@
    +    $this->entityTypeManager = $entity_manager;
    

    The parameter should be $entity_type_maanger.

mglaman’s picture

Caught in the act of a bold face copy paste.

  • mglaman committed 2a85805 on 8.x-2.x authored by jsacksick
    Issue #2887061 by jsacksick, mglaman, lawxen, GoZ, chrisrockwell:...
mglaman’s picture

Assigned: mglaman » Unassigned
Status: Needs review » Fixed

🥳 Committed!

21piyush’s picture

Hi,

After update the Drupal core to Drupal 8.9.3 after that re-apply the patch and got the error as

An HTTP AJAX error has occurred. HTTP status code: 200 Debugging information below. Path: / promotion / 1 / edit? Destination = / admin / commerce / promotions & ajax_form = 1 StatusText: parsererror ResponseText: "

can you please help to solve me this asap as the site is already live and we update it and got this error.

Thank you

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

pieterdt’s picture

Hi,

Not sure if this is related to the fact that the patch works against 2.x, but even though it applies to 2.20, it doesn't work.

I had to fix L83 in PurchasedEntityConditionBase.php:

$entity_ids = $this->entityUuidMapper->mapToIds($this->getPurchasableEntityType(), $this->configuration['entities'] ? $this->configuration['entities'] : []);

Without that fix, I get an error in the logs that the second argument for mapToIds is null instead of an array.

Also, while reviewing the patch, I noticed a typo in line 90 for PurchasedEntityConditionBase.php should be

'#title' => $purchasable_entity_type->getCollectionLabel(),

instead of

'#title' => $purchasable_entity_type->getCollectionLabsel(),

see https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/order...

ELC’s picture

This code needs a bit more polish re #43 to avoid errors for both new and existing promotions after this code is added.

The configuration issue needs to have the entities key returned as part of defaultConfiguration() to get rid of the errors.

The typo is obvious.

Attached is an additional patch for current dev state (essentially an interdiff) plus the full patch.

  • jsacksick committed 3d80495 on 8.x-2.x authored by ELC
    Issue #2887061 followup by ELC: Fix a typo and the default configuration...
jsacksick’s picture

Status: Needs review » Fixed

Good catch, not sure how this has been unnoticed for that long :). Thanks!

mglaman’s picture

+++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionBase.php
@@ -87,7 +96,7 @@ abstract class PurchasedEntityConditionBase extends ConditionBase implements Con
-      '#title' => $purchasable_entity_type->getCollectionLabsel(),
+      '#title' => $purchasable_entity_type->getCollectionLabel(),

🤦🏼‍♂️sorry, everyone.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

ryanfc78’s picture

I am wondering if there can be a bit of an expansion on this. In my store with Drupal 8/9 I have a Product Attribute of Department Name. Each product variation has a drop down where that product is assigned a Department Name (because some products are only available in certain departments). I want to apply a promotion with a coupon code where I can limit that code to only work for products that have that department name assigned to them. A similar example would be I also have a Product Attribute for size. If I wanted to just limit that Promotion Code by the size XXL I don't see a way to do that in the current restrictions. Other than selecting every single SKU but that would take ages with the amount of products I have. Unless I am missing something.