Problem/Motivation

I have a use case where a product needs multiple variation types. For example a book with the following variations:

1. Hardcopy (variation type 1)
2. Paperback (variation type 1)
3. PDF file (variation type 2)
4. Audio file (variation type 2)

Where variation type 1 works fine as a regular product variation, but variation type 2 is working with commerce_license and a D8 port of commerce_file. Product option 1 and 2 (Hardcopy and Paperback) will be shipped and will NOT include a file or license. Where options 3 and 4 will NOT be shipped, but will include a downloadable file and a license (handled by commerce_license / commerce_file).

Proposed resolution

It would be great if I could use different variation types for the same product, then they can be used as different options for the same product, instead of splitting the different options into two different products (when they are the same product – same title, description, images etc.).

Remaining tasks

Figure out the best approach and write some codes.

User interface changes

This may include checkboxes or multi-select field for product variation type on the product type settings page. But also maybe a way to select default variation type. If the current product variation type is used as the default that might help make the change a little less intrusive.

Maybe a select box for product variation type on the add variation page that appears only if a product type has more than one variation type option.

Original report

I’m adding what looks like a related issue #2880602: One Product types for many Product variation types. Also adding the commerce_file port as a related issue. I realize that there will be other issues to face for this use case, but this seems like a good start.

https://www.drupal.org/project/commerce_file/issues/2875904#comment-1330...

Any feedback on this issue, reasons not to use this approach, or maybe direction on where to start if an additional contrib module is a better option, or where to start for a patch, would be appreciated.

Issue fork commerce-3089040

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

Nathaniel created an issue. See original summary.

xandeadx’s picture

+1 for feature.

carlitus’s picture

another +1

mahtabalam’s picture

+1

scorpionghost’s picture

+1

john.oltman’s picture

+1

calmforce’s picture

+1

brunomolica’s picture

+1

dilovan’s picture

+1

dwkitchen’s picture

StatusFileSize
new61.84 KB

I think we have a solution for this, but you won't be able to use the Commerce Core Add to Cart From in the Book/eBook scenario.

The standard form assumes that all the products are going to use the same order item type, and physical and digital products have different order item type.

Individual variations will need their own Form, but this can be done with https://www.drupal.org/project/commerce_variation_cart_form is this an acceptable solution for most people?

Here is an example product:
Multivariation product screen capture

tahiticlic’s picture

@dwkitchen but how do you associate multiple variations of different types to the product ?

carsteng’s picture

@dwkitchen The solution to your problem with the order_item_types can be found here:
https://www.drupal.org/project/commerce/issues/3017662

jsidigital’s picture

Did anyone get this working without having to give each variation its own separate form? I’m not a fan of the UI/look of that.

Really need to be able to have one product with both shippable and download variation.

dwkitchen’s picture

@jsidigital you can use Inline Entity Form on the main product form if that is better than using the variations tab.

I'm going to be making some updates to make this a better transition feature so it might go into 2.x as experimental.

dwkitchen’s picture

Making this more transitional-able; so it can be released in an experimental feature in 2.x, so here is the expected update that will need to be part of 3.x

/**
 * Update existing product type configuration for multi-variation type.
 */
function commerce_product_update_8300() {
  $bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo('commerce_product');
  $product_types = array_keys($bundle_info);
  $config_factory = \Drupal::configFactory();
  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_field_manager = \Drupal::service('entity_field.manager');

  $entity_type_manager->clearCachedDefinitions();
  $entity_field_manager->clearCachedFieldDefinitions();

  foreach ($product_types as $bundle) {
    $configuration = $config_factory->getEditable('commerce_product.commerce_product_type.' . $bundle);
    if (!$configuration->get('variationMultipleTypes') && $variationType = $configuration->get('variationType')) {
      $configuration->set('variationTypes', [$variationType]);
    }
    $configuration->clear('variationType');
    $configuration->clear('variationMultipleTypes');
    $configuration->save();
  }
}
recidive’s picture

StatusFileSize
new3.99 KB

@dwkitchen attached is a "work in progress" patch on top of your MR for making the build-in add to cart form work with multiple variation types.

Let's discuss how we can approach that taking into account backwards compatibility. Maybe this can be done in a custom set of formatters/widgets.

Let me know what you think. And if you have the chance, please test it on your setup and let me know if it works or not.

Thanks!

griz’s picture

+1 for this feature which we relied on in Commerce 1 and is now missing in Commerce 2.
I've read the reasoning around removing this and really wish I'd been around to point out the importance of being able to reference multiple variation types from a product type for this exact reason.

I suspect the core commerce team will be reluctant to make such a large change at this stage, even if the community provide a solid and well-tested patch.

An alternative method may be to get the 'shippable' status of a product variation from one of its attributes.
For those of us who sell documents, that attribute would be 'Document Format' or something similar. Though I don't know if it would be possible to hide the shipping pane in checkout, and this would also depend on the weight field not being required in the variation edit form (and preferably being hidden by AJAX when a non-shippable attribute value is selected). The simplest and solution really seems to be to have a separate variation type. So again, please, +1.

ginovski’s picture

Agreed, this is needed, a lot. +1

nathaniel’s picture

@griz as a workaround I ended up adding a custom shipping information checkout pane with the following code and setting the weight to 0 on digital variations.

... extends ShippingInformation {

public function isVisible() {
    if (!$this->order->hasField('shipments')) {
      return FALSE;
    }

    // The order must contain at least one shippable purchasable entity.
    foreach ($this->order->getItems() as $order_item) {
      $purchased_entity = $order_item->getPurchasedEntity();
      // Check if the product variation weight is set and greater than zero.
      if ($purchased_entity && $purchased_entity->hasField('weight') && $purchased_entity->weight->number > 0) {
        return TRUE;
      }
    }

    return FALSE;
  }

...

sander wemagine’s picture

+1

I need it for the same reason. Having a webshop that ships physical books ánd e-books. Would be awesome if we can select a shippable and non-shippable product variation on the same product, to prevent having 2 identical products-entity with all the same fields.

recidive’s picture

Got this error when trying to create a new product type without multiple variation types behavior. Looks like the old variation type select is not working.

[17-Mar-2021 17:36:00] WARNING: [pool www] child 1026 said into stderr: "NOTICE: PHP message: TypeError: Argument 1 passed to Drupal\Core\Form\FormState::setError() must be of the type array, null given, called in /var/www/html/web/modules/contrib/commerce/modules/product/src/Form/ProductTypeForm.php on line 216 in /var/www/html/web/core/lib/Drupal/Core/Form/FormState.php on line 1093 #0 /var/www/html/web/modules/contrib/commerce/modules/product/src/Form/ProductTypeForm.php(216): Drupal\Core\Form\FormState->setError(NULL, Object(Drupal\Core\StringTranslation\TranslatableMarkup))"
[17-Mar-2021 17:36:00] WARNING: [pool www] child 1026 said into stderr: "#1 [internal function]: Drupal\commerce_product\Form\ProductTypeForm->validateForm(Array, Object(Drupal\Core\Form\FormState))"
[17-Mar-2021 17:36:00] WARNING: [pool www] child 1026 said into stderr: "#2 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(82): call_user_func_array(Array, Array)"
[17-Mar-2021 17:36:00] WARNING: [pool www] child 1026 said into stderr: "#3 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(273): Drupal\Core\Form\FormValidator->executeValidateHandlers(Array, Object(Drupal\Core\Form\FormState))"
[17-Mar-2021 17:36:00] WARNING: [pool www] child 1026 said into stderr: "#4 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(118): Drupal\Core\Form\FormValidator->doV..."
2021/03/17 17:36:00 [error] 1021#1021: *1186 FastCGI sent in stderr: "PHP message: TypeError: Argument 1 passed to Drupal\Core\Form\FormState::setError() must be of the type array, null given, called in /var/www/html/web/modules/contrib/commerce/modules/product/src/Form/ProductTypeForm.php on line 216 in /var/www/html/web/core/lib/Drupal/Core/Form/FormState.php on line 1093 #0 /var/www/html/web/modules/contrib/commerce/modules/product/src/Form/ProductTypeForm.php(216): Drupal\Core\Form\FormState->setError(NULL, Object(Drupal\Core\StringTranslation\TranslatableMarkup))
#1 [internal function]: Drupal\commerce_product\Form\ProductTypeForm->validateForm(Array, Object(Drupal\Core\Form\FormState))
#2 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(82): call_user_func_array(Array, Array)
#3 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(273): Drupal\Core\Form\FormValidator->executeValidateHandlers(Array, Object(Drupal\Core\Form\FormState))
#4 /var/www/html/web/core/lib/Drupal/Core/Form/FormValidator.php(118): Drupal\Core\Form\FormValidator->doV" while reading response header from upstream, client: 172.19.0.6, server: _, request: "POST /admin/commerce/config/product-types/webhosting/edit?destination=/admin/commerce/config/product-types HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm.sock:", host: "d8dev.ddev.site", referrer: "http://d8dev.ddev.site/admin/commerce/config/product-types/webhosting/edit?destination=/admin/commerce/config/product-types"
recidive’s picture

StatusFileSize
new4.37 KB

A re-roll of my patch in #17 which applies on top of the issue MR. I added a check for changing the order item type before rebuilding the form with a different form display.

It's still not working when the product variation has a different set of attribute types, e.g. one variation has color and the other has color + size variations. I'm fixing it and will incorporate it in the MR.

jsidigital’s picture

@recidive
Is your patch similar to the preview image on comment #10?

Just wondering if this shows multiple "Add to cart" buttons on the same product page like comment #10 or if it works more like product variations where only one "Add to cart" button is visible at a time?

Thanks.

recidive’s picture

@jsidigital it works like the standard Commerce add to cart form. My patch makes it compatible, but it's not ready. It works in the case your product variations have the same set of attributes, like "size" and "color" with different attribute values.

khiminrm made their first commit to this issue’s fork.

recidive’s picture

Just found a problem with the code introduced in #2952529: Support for Layout Builder module. I'm unable to create products from a user different than uid 1 with patch from the the merge request. I get:

"NOTICE: PHP message: Uncaught PHP Exception Drupal\Core\Entity\Exception\AmbiguousEntityClassException: "Multiple entity types found for "Collection" supports multiple variation types.." at /var/www/html/docroot/modules/contrib/commerce/modules/product/src/Entity/ProductType.php line 130"

There are calls to $product_type->getVariationTypeId() that needs to be handled properly.

danmer’s picture

I have faced an issue with this patch.
In dblogs we have errors like this:
Notice: Undefined index: target_bundles in Drupal\commerce_product\Plugin\Field\FieldFormatter\AddToCartFormatter::isApplicable() (line 111 of /var/www/html/web/modules/contrib/commerce/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php)
Notice: Undefined index: target_bundles in Drupal\commerce_product\Plugin\Field\FieldFormatter\ProductAttributesOverview::isApplicable() (line 243 of /var/www/html/web/modules/contrib/commerce/modules/product/src/Plugin/Field/FieldFormatter/ProductAttributesOverview.php)
It happens because for some fields $handler_settings in isApplicable() can be empty array.
We can add something like this:
$target_bundles = array_key_exists('target_bundles', $handler_settings) ? $handler_settings['target_bundles'] : NULL;
or similar code.
We need to provide additional checking to avoid such issues.

danmer’s picture

Status: Active » Needs review
StatusFileSize
new3.44 KB

I have updated the patch because AddToCartFormatter already has the changes from previos patch

dwkitchen’s picture

Status: Needs review » Needs work

Patch #29 is missing many of the other changes in the MR: https://git.drupalcode.org/project/commerce/-/merge_requests/25

danmer’s picture

Status: Needs work » Needs review
StatusFileSize
new37.18 KB

Here is the updated patch

dwkitchen’s picture

Status: Needs review » Needs work

The last submitted patch, 31: multi-variations-add-to-cart-form-3089040-31.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

b_sharpe’s picture

Seems the MR is failing a lot of tests on

Notice: Undefined index: target_bundles in /var/www/html/modules/contrib/commerce/modules/product/src/Plugin/Field/FieldFormatter/AddToCartFormatter.php on line 111

  • dwkitchen authored 4a6120d on 3089040-single-product-with
    Issue #3089040: Single product with multiple variation types
    
  • c824302 committed on 3089040-single-product-with
    Issue #3089040: Single product with multiple variation types, update...
  • dwkitchen authored df1499e on 3089040-single-product-with
    Issue #3089040: Continue to support getVariationTypeID()
    

jsacksick made their first commit to this issue’s fork.

jsacksick’s picture

Without reviewing the rest, I just pushed a commit that should in theory address some of the test failures.

geek-merlin’s picture

Status: Needs work » Needs review

Great, we are green! A quick vgrep through the comments does not give me reasons for NW, so setting NR.

Can anyone who is deeper in this issue list what is needed.

- Code review
- Additional tests?
- ...?

jsacksick’s picture

Hm, noticed the patch has now changes from @recidive, without the changes to the AddToCartFormatter::isApplicable() method, which means the changes made to the product variation widget are basically unnecessary as is :p.

jsacksick’s picture

Status: Needs review » Needs work
StatusFileSize
new34.86 KB

This needs more work, focused on fixing the tests, then manually started to test the actual changes, and stumbled upon an access denied on the route for adding variations (i.e product/{commerce_product}/variations/add). See screenshot below, as user 1:

jsacksick’s picture

Ok, this was probably related to the fact that I didn't rebuild caches, prior to creating a new product type.

Then realized the strict check that doesn't allow changing the variation type setting is really annoying, that probably needs to be revisited, but leaving it for now.

Also, we need to pass the variation types setting through array_filter before saving it, instead of doing that everywhere in the code.

jsacksick’s picture

Status: Needs work » Needs review

Pushed additional fixes, I filter the list of variation types from ProductTypeForm::validateForm() instead of saving the setting incorrectly, so that we don't have to call array_filter() everytime we call $product_type->getVariationTypeIds();.

Note that I remove the additions to

AddToCartForm::isApplicable()

as the Product variation title widget seems to work thanks to the changes made to ProductVariationWidgetBase::ajaxRefresh() although it doesn't feel like the right place for this code... Rebuilding the order item should probably happen from the add to cart form itself (or perhaps the ProductLazyBuilder?), but well, tried that, and couldn't figure it out quickly.

b_sharpe’s picture

Simply posting current MR patch for composer stability. Still needs further review.

jsacksick’s picture

+++ b/modules/product/commerce_product.install
@@ -20,6 +20,13 @@ function commerce_product_install() {
+  //@todo if a module has single variation config update it to multi-variant

I'm not sure we'd want to do anything on hook_modules_installed().

I'm pretty sure we could implement a preSave() method in ProductType.

If the variation type setting is set, and not the variation types, we basically sett he variation types setting.

jsacksick’s picture

@b_sharpe: Your diff seems incorrect, how did you produce it? The code looks wrong there....

  1. +++ b/modules/product/commerce_product.install
    @@ -292,3 +299,26 @@ function commerce_product_update_8212() {
    +function commerce_product_update_8213() {
    

    There's no update hook in the merge request...

  2. +++ b/modules/product/src/ContextProvider/ProductVariationContext.php
    @@ -99,7 +99,7 @@ class ProductVariationContext implements ContextProviderInterface {
    +            $value = $this->sampleEntityGenerator->get('commerce_product_variation', $product_type->getVariationTypeIds());
    

    This isn't the right version of the patch, for sure...

b_sharpe’s picture

Well, I learned something today... I'm still not quite sure WHY this is, but I grabbed it from https://git.drupalcode.org/project/commerce/-/merge_requests/25.patch which I thought was correct, but it turns out it is https://git.drupalcode.org/project/commerce/-/merge_requests/25.diff (diff vs patch extension)

This is extremely confusing and I can't find any docs as to what the difference actually is here... but anyhow heres the diff version which I THINK is correct.

jsacksick’s picture

Version: 8.x-2.x-dev » 3.0.x-dev
Status: Needs review » Needs work

We now have a 3.0.x branch, we could try squezing this in, though this needs additional changes. The previous setting would probably need to be removed.

$role->grantPermission("manage {$product_type->getVariationTypeIds()} commerce_product_variation");

This doesn't look correct as well.

If we remove the setting, that also means we need to remove the associated methods, which could probably break custom/contrib code.

I need to review this afresh.

bradjones1’s picture

Regarding the question in #48 - I tried to find a deep link in GitLab to explain the difference, but I can say from knowledge and experience that the patch version is basically each commit's patchfile appended in sequence, so they are applied in that same order. The diff only contains the diff from the HEAD of the MR to the target branch. So if for instance there were files or lines added and then deleted/reverted in commits "along the way," you won't see any of that in the diff. But you'll get all that noise in the patch. From my experience, even if they get you to the same endpoint there can be dragons in applying the patch instead of the diff, if you "just want to get the MR as a patch."

bradjones1’s picture

The MR looks like it handles the add-to-cart "fine" with a pretty vanilla implementation, despite the warning message displayed when enabling multiple variation types on the product type.

However, after some testing it looks like we need to:

  1. Add a constraint to the data model/entity validation to ensure that the different variation types on the product type have compatible/identical attributes (see discussion at #23)
  2. Or, determine some sort of default UI handling for dissimilar attributes (not sure this is even possible.)

Error with two product variation types, one of which does not have the "size" attribute:

InvalidArgumentException: Unknown attribute field name "attribute_size". in Drupal\commerce_product\Entity\ProductVariation->getAttributeValue() (line 343 of modules/contrib/commerce/modules/product/src/Entity/ProductVariation.php).

jsacksick’s picture

Version: 3.0.x-dev » 8.x-2.x-dev
Status: Needs work » Needs review
StatusFileSize
new51.75 KB

Came back to this today. Decided to take a stab at:

InvalidArgumentException: Unknown attribute field name "attribute_size". in Drupal\commerce_product\Entity\ProductVariation->getAttributeValue() (line 343 of modules/contrib/commerce/modules/product/src/Entity/ProductVariation.php).

What I did was simply falling back to the "title" widget when a product references more than a single variation type. It's a simple approach, but I'd say this is good enough for now to get this feature released...

Thoughts? :p

Status: Needs review » Needs work

The last submitted patch, 52: 3089040-52.patch, failed testing. View results

jsacksick’s picture

Status: Needs work » Needs review
StatusFileSize
new54.72 KB

Had a discussion with @rszrama and it was decided to remove the "multipleVariationTypes" setting. From the product type form the user has to choose between using one or multiple existing product variation types or create a new one.

Hopefully this patch doesn't fail like the previous one :).

jsacksick’s picture

StatusFileSize
new54.74 KB
new444 bytes
dwkitchen’s picture

Status: Needs review » Needs work

Feels like we are 99% there; I have found one issue.

In a site with multiple product types including apparel and media, it will generally have physical_product order item type set to use the "Product Variation Atributes" widget on the add to cart form to account for the apparel.

However, if this is the case the add to cart form on a media product with two variation types each using a different order item type i.e. physical_product and license_product you can only add the first variation to the cart.

If all order item types use the "Product Variation Title" widget the add to cart works fine, and the current workaround is to add an additional order item type.

jsacksick’s picture

Status: Needs work » Needs review
StatusFileSize
new59.31 KB
new12.13 KB

The attached patch should resolve the issue from #56.

Also decided to change the logic that consists in rebuilding the form if the the new selected variation is supposed to use a different order item type, this is no longer done from the ajax callback (it seemed like an odd place to do this).
Rebuilding the form a second time surely has a performance impact, but not really sure we can skip this...

Let's see if the existing tests pass, though we could definitely use additional tests to ensure the add to cart works properly when a product references variations of a different type.

dwkitchen’s picture

Status: Needs review » Reviewed & tested by the community

The issue I found in #56 has now been resolved. Add to cart works with both Title and Attribute options.

jsacksick’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new61.71 KB

I added tests coverage, this now can be safely committed. One thing that isn't customizable is the title widget settings when we're forcing the use of the title widget when the product references variations of a different type, but I guess this could be addressed in a followup.

  • jsacksick committed 42765de on 8.x-2.x
    Issue #3089040 by jsacksick, dwkitchen, danmer, recidive, b_sharpe,...

  • jsacksick committed 1f7f681 on 3.0.x
    Issue #3089040 by jsacksick, dwkitchen, danmer, recidive, b_sharpe,...
jsacksick’s picture

Status: Needs review » Fixed

Committed!! Yay!

geek-merlin’s picture

Yay! 🍾
Thanks everyone, this is so useful.

Status: Fixed » Closed (fixed)

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

mrweiner’s picture

Is it expected that we should be able to assign additional product variation types to a product type for which products already exist? If I apply the patch and edit a product type, the Variations section is greyed out and I see "Products exist for this product type, you can no longer edit this setting."

jsacksick’s picture

The current behavior was kept... But this kind of makes this feature unusable for existing product types... Now that I think of it. I can't remember what the rationale was behind the initial decision...

@mrweiner: Would you mind opening a new issue? We should probably just make sure that you can't unselect a variation type that was previously selected, but allow choosing "new" ones.

mrweiner’s picture

Yeah looking through the issue it seems like maybe the feature was just forgotten about/overlooked. Just opened up #3270838: Allow additional variation types to be added to a product type with existing products..

adam1’s picture

I'm going to set up a book shop with Drupal 10, which offers printed books and open access books for download, so I came across this issue. Can some one tell me if this patch is incorporated in Drupal 10 or do I have to patch? Since it's a community project that has not much funding, I don't like to do patches because future maintenance may get harder.

john.oltman’s picture

Hey adam1, as noted in comments 60-62 and the issue status of "Closed (fixed)" a patch is not necessary - this feature has been released.

adam1’s picture

Thank you for clarifying!

ccjjmartin’s picture

I am proposing a change to the code that was added in this issue. Would appreciate some more eyes on this bug to verify it doesn't break anything introduced here: https://www.drupal.org/project/commerce/issues/3511232