Problem/Motivation

With the changes to the Discount UI, the name property in the discount's price component, which had consistently held the value "discount", can now take on any user defined value. This makes it impossible to reliably identify through the name property those price components that are discounts.

This change broke my custom module, but I based my module on the commerce_discount_date and commerce_discount_usage modules, so I suppose those modules may not function correctly now if the name value is changed by a user.

I think the only way to reliably identify a discount now is indirectly, by inspecting the $component['price']['data'] array to see if it contains a key called "discount_name".

Proposed resolution

An explicit method of identifying any type of price component would be a good idea, imho.

Price components could have a price_component_type identifier in addition to the name, price and included fields.

Alternatively, the name fields could be removed/hidden from the UI so the end user would not be able to modify the value.

This is somewhat related to this issue regarding component titles: https://drupal.org/node/2034685

Comments

jkuma’s picture

Hi vuzzbox,

Thanks for your very accurate post! I was encountered the same issue when I was implementing that feature request #2034685: Discount component price names. Unfortunately, we can't change the component price system, so that's why I took the decision to alter the component price name for two reasons:

  • To display discount component price on multiple line items, and not merging all components in the same array.
  • Drupal commerce doesn't provide a way to do it differently... When you are displaying a component price, the name property is used to identify a component type and also used as a label.

Regarding your proposals, here are my answers:

An explicit method of identifying any type of price component would be a good idea, imho.

Yes, it could be definitively very useful for your custom_module and others modules. So, if you want to take that way in order to solve your issue, please let me know and i'll create a patch for it.

Price components could have a price_component_type identifier in addition to the name, price and included fields.

This issue is not related with commerce_discount, but it's dependents of commerce module. Feel free to open an issue in commerce and you can link that post as an example of implementation.

Alternatively, the name fields could be removed/hidden from the UI so the end user would not be able to modify the value.

No. This feature request was long-awaited by merchants and helps user to identify which discounts are currently applied on order. From my perspective, Merging every discount component price in the same container is not relevant for customers.

vuzzbox’s picture

Title: Define a Price Component Type » Discount price component name is set incorrectly in price calculation rules
Category: feature » bug
StatusFileSize
new665 bytes

Thanks for your reply goldorak.

I completely understand and agree with your points. However, I've looked into it a little deeper and I think that something unexpected is happening, a bug really. I'm changing the title of the issue to make it more accurate, changing the category to bug report and providing a patch.

It turns out that Commerce core (or Commerce Pricing) already defines price component types and when calculating the sell price, it stores the name of the component type in the "name" property of the component (why not call it "type"??)

I think the commerce_discount module is inadvertently setting that name value (i.e. price component type) incorrectly during the sell price calculations.

In the code below, I am showing the structure of the price components for a single line item that has two discounts applied to it, during the sell price calculation:

components[3]
  [0]
  	name='base_price'
  	price[3]
	  amount="1500"
	  currency_code="USD"
	  data[1]
	  	components[0]
	included=true
  [1]
  	name="Some user defined discount name - 10% off"
  	price[3]
  	  amount=-150
  	  currency_code="USD"
  	  data[2]
  	  	discount_name="some_user_defined_discount_name_10_off"
  	  	discount_component_title="Some user defined discount name - 10% off"
  	included=true
  [2]
  	name="A second user defined discount name - $2 off"
  	price[3]
  	  amount=-200
  	  currency_code="USD"
  	  data[2]
  	  	discount_name="second_user_defined_discount_name_2_off"
  	  	discount_component_title="A second user defined discount name - $2 off"
  	included=true

You'll note that the values in components[N]['name'] and components[N]['price']['data']['discount_component_title'] are identical. However, the commerce_pricing module wants the "name" value to represent the name of price component type (see the function definition below.) Based on that, I think "name" should be a fixed value for each type of component, and not a user-defined value.

In commerce_discount.rules.inc, the commerce_discount_add_price_component function calls the commerce_price_component_add function on line 361:

  $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
    $line_item_wrapper->commerce_unit_price->value(),
    empty($component_title) ? 'discount' : $component_title,
    $difference,
    TRUE
  );

The second value passed to the function, empty($component_title) ? 'discount' : $component_title, contains either the discount_component_title or 'discount', but, again, per the function definition, that should be the component type name only, i.e., 'discount'.

Here's the commerce_price_component_add function definition from commerce_price.module:

/**
 * Adds a price component to a price's data array.
 *
 * @param $price
 *   The price array the component should be added to.
 * @param $type
 *   The machine-name of the component type to be added to the array.
 * @param $component_price
 *   The price array for the component as defined by the price field.
 * @param $included
 *   Boolean indicating whether or not the price component has already been
 *   included in the price the component is being added to.
 * @param $add_base_price
 *   Boolean indicating whether or not to add the base price component if it is
 *   missing.
 *
 * @return
 *   The updated data array.
 */
function commerce_price_component_add($price, $type, $component_price, $included, $add_base_price = TRUE) {
  // If no price components have been added yet, add the base price first.
  if ($add_base_price && empty($price['data']['components']) && $type != 'base_price') {
    $price['data'] = commerce_price_component_add($price, 'base_price', $price, TRUE);
  }

  $price['data']['components'][] = array(
    'name' => $type,
    'price' => $component_price,
    'included' => $included,
  );

  return $price['data'];
}

So, I think the call to the function should be changed to:

  $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
    $line_item_wrapper->commerce_unit_price->value(),
    'discount',
    $difference,
    TRUE
  );

I've tested the patch and discount_component_title remains unchanged and the still presents correctly on the cart, checkout pages, etc.

Let me know what you think, goldorak.

Thanks!

jkuma’s picture

Hello vuzzbox,

Thanks for your patch but unfortunately that doesn't solve the issue.

e.g - here is a screenshot without your patch applied :
without patch
And the screenshot bellow is the one with your patch applied:
patch applied

As you can see, the line items have been combined in one line item. Why? During the commerce_order_calculate_total($order), Commerce is combining the line item's component prices into the order total. If you take a closer look to that function:

function commerce_price_components_combine($price, $price2) {
......
    // Look for a matching component in the base price data array.
    $matched = FALSE;

    foreach ($price['data']['components'] as $base_key => $base_component) {
      // If the component type matches the component in question...
      if ($base_component['name'] == $component['name']) {
        // Add the two prices together and mark this as a match.
        $price['data']['components'][$base_key]['price']['amount'] += $component['price']['amount'];

        $matched = TRUE;
      }
    }

    // If no match was found, bring the component in as is.
    if (!$matched) {
      $price['data']['components'][] = $component;
    }
.....

So all the component prices with the same name are merged into the same component price. So, if we are using the name "discount" for every discount component prices, we can't split the component prices , that's why I took the decision to change the name when creating a discount component price. I know it's a dirty solution, but I can't find an another way to do it....

vuzzbox’s picture

Right! I see that is the problem. Thanks for providing that detail. It happens that the module I am writing eliminates all but the single highest valued discount that applies to a line item. I was only seeing only one discount per item with or without the patch.

Either way, I need a simple, obvious value to test if a component is a discount. So how about a compromise: why not prepend "discount-" to the name of a discount component? For example, "discount-prod1 10%", "discount-prod2 35%"

That would consistently identify discount components, but still provide the distinct values that are needed to present each component individually.

Something to consider.

Thanks!

vuzzbox’s picture

And in my opinion, it would also keep the commerce discount components in closer compliance with the intent of the architecture of Commerce Pricing module.

jkuma’s picture

Status: Active » Needs work

Either way, I need a simple, obvious value to test if a component is a discount. So how about a compromise: why not prepend "discount-" to the name of a discount component? For example, "discount-prod1 10%", "discount-prod2 35%"

Ok vuzzbox, let's do it this way.

jkuma’s picture

Status: Needs work » Needs review
StatusFileSize
new593 bytes

Let me know if it fits your needs.

jkuma’s picture

Assigned: Unassigned » jkuma
vuzzbox’s picture

Status: Needs review » Reviewed & tested by the community

Perfecto!

jkuma’s picture

Committed in 7.x-1.x-dev branch.

jkuma’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

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