Hello all,

first off : good module, despite some hickups along the way.

I've noticed a very disturbing bug with percentage based discounts using 1 % (or lower) discounts. This bug applies to either order based discounts or product based discounts.

When applying 1% as the discount amount the discount totals the exact amount of the order / product. This is a CRITICAL bug to say the least!

I've traced the problem down to this line in commerce_discount_rules :

$discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount_name);
  $rate = $discount_wrapper->commerce_discount_offer->commerce_percentage->value();
  // Get the line item types to apply the discount to.
  $line_item_types = variable_get('commerce_discount_line_item_types', array('product' => 'product'));

  if ($rate > 1) {
    $rate = $rate / 100;
  }

As you can see, the $rate is extracted by the wrapper into a 'float' number, which is then divided by 100 for use in these lines :

Order based discounts :

$calculated_discount += $line_item_total['amount'] * $rate;

Product Based discounts

$calculated_discount = $unit_price['amount'] * $rate * -1;

One could easily change the if ($rate > 1) to if ($rate > 0) and make it work, but the big question here is : Why do you have a validation here at this point in the code? The module is using a form to gather the values, why not use a validate function to limit the value entered in the form itself? That way the whole $rate check could be thrown away, because as it stands now, one can enter negative values without outputting an error, which is bad.

The code dealing with applying the discount percentage should not be responsible for checking if it received a good value, that should be the responsability of the form validator.

I'll try to create a patch for this.

Comments

Alienpruts’s picture

Assigned: Unassigned » Alienpruts
alexrayu’s picture

Are you suggesting to remove the condition at all? Like this? :

-  if ($rate > 1) {
    $rate = $rate / 100;
-  }
Alienpruts’s picture

Issue summary: View changes
StatusFileSize
new1.73 KB

OK, enclosed patch does the following :

1) removes check from rules code and applies rate / 100 directly. It should not have to worry about getting wrong values, since

2) added validation for the percentage field to be always positive, excluding zero. A user cannot enter a non-working value (negative numbers).

I struggled however to have the percentage field get highlighted in red indicating where the error should be. That is because it's a composed field name (it resides in a form within a form (is that even correct? :) ). I tried several times but gave up.

Advice always welcome.

Alienpruts’s picture

Issue summary: View changes
Status: Active » Needs review
Alienpruts’s picture

StatusFileSize
new1.75 KB
new769 bytes

Made a mistake in previous patch (you couldn't set a fixed amount anymore).

Attached : new patch + interdiff.

alexrayu’s picture

Thanks for the patch, Alienpruts.

Tested the patch successfully, including the admin ui part.

There is a round up part from my prev. task #2458621: Discount rate mis-calculated for discount percentages < 1 that has not been carried over, but that may be another ticket, since has nothing to do with validation. Maybe makes sense to leave it as it is in this patch, and hope for faster RTBC and inclusion upstream.

Alienpruts’s picture

Hello alexrayu,

indeed, i believe those two tickets are seperate issues, and should reside in their own patch. Good work, thank you.

deggertsen’s picture

Tested and it appears to be working fine.

Thanks for the patch.

joelpittet’s picture

Status: Needs review » Needs work

I'm a fan of this, it cleans things up a bit. So big +1 to this.

Just needs those TODOs resolved though I do like the intent behind them. They also need the LANGUAGE_NONE constant instead of 'und'.
And to be nice an update hook to convert any previous values form decimal to out of 100.

  1. +++ b/commerce_discount.rules.inc
    @@ -178,14 +178,10 @@ function commerce_discount_fixed_amount(EntityDrupalWrapper $wrapper, $discount_
    -  if ($rate > 1) {
    -    $rate = $rate / 100;
    -  }
    

    So this will prevent people from using 0.5 as 50%. That makes sense, but maybe we should add an update hook in case people were using this hidden feature?

    Just needs to loop through the field_data_commerce_percentage table and check if there are any percent values and convert them.

  2. +++ b/includes/commerce_discount.admin.inc
    @@ -278,6 +278,12 @@ function commerce_discount_form_validate($form, &$form_state) {
    +    // TODO : form_set_error does not highlight the percentage box (wrong element name?)
    

    This todo should be resolved before commit but I like the idea.

joelpittet’s picture

Issue tags: +Commerce Sprint

Bumping

Alienpruts’s picture

Hello joelpittet,

Completely forgot about this patch until your bump, sorry.

The issue is that I do not have the time to be working on this ATM, because I would have to set up a new sandbox project to do so (the project I was working on when writing this patch took a different approach to discounts alltogether, so the module got ditched). Because of my busy work schedule I won't be able to do the work you proposed.

However, I'm attending a DrupalCamp this weekend, so maybe, just maybe, if I find the time in between sessions I might be able to incorporate your feedback into the patch. But no promises given :)

Tnx again for the feedback.

joelpittet’s picture

Version: 7.x-1.0-alpha4 » 7.x-1.x-dev
Issue tags: -percentage, -percent, -discount, -rate, -validation

Those tags don't really help, removing.

Thanks, hopefully you have some time @Alienpruts. I can help with the upgrade path if you want and you can just deal with the @todos?

Alienpruts’s picture

Tnx for the offer, joelpittet, i'll try it on my own first (for learning purposes). Should I hit a roadblock, I'll be sure to call for help with the update hook.

Timing? Not this weekend, but I can work on it on the way to work on the bus, so expect something this week.

Tnx again.

mglaman’s picture

Alienpruts’s picture

Status: Needs work » Needs review
StatusFileSize
new3.22 KB

Ok, here is the improved patch, ready for review.

Changes :

  • und changed for LANGUAGE_NONE
  • Finally got the percentage field outlined in red when a wrong value is entered
  • Added an update hook to loop through existing percentage values and change them, if necessary

This was my first ever update hook written (I know, strange isn't it?), so feedback would be much appreciated.

I did not supply an interdiff, because I rerolled the patch against the last dev version.

Status: Needs review » Needs work

The last submitted patch, 15: percentage_based-2468159-15.patch, failed testing.

mglaman’s picture

+++ b/includes/commerce_discount.admin.inc
@@ -304,6 +304,11 @@ function commerce_discount_form_validate($form, &$form_state) {
+  // TODO : should we introduce more form validation?
+  $percentage = $form_state['values']['commerce_discount_fields']['commerce_discount_offer'][LANGUAGE_NONE]['form']['commerce_percentage'][LANGUAGE_NONE][0]['value'];
+  if (!empty($percentage) && $percentage <= 0) {
+      form_set_error('commerce_discount_fields][commerce_discount_offer][und][form][commerce_percentage', t('Percentage should be a positive number, excluding zero!'));

I think we can fix this @TODO by using form_error() and passing the form element. That will read #parents and set error properly.

I meant to roll a patch last night but failed. That's how the field API sets errors I believe.

EDIT: Just saw previous comment say element was highlighting. We're still hard coding [und], though, which is why mentioned solution would be useful.

mglaman’s picture

Status: Needs work » Needs review
StatusFileSize
new3.41 KB
new1.33 KB

Here's updated patch to fix tests.

Alienpruts’s picture

Ah, I see now why the test failed. Tnx for the fix mglaman.

About the form_error solution : wouldn't that mean that we would have to pass in each form element to that function for every element we would have to validate? Granted, at the moment, there's only one validation(hence the TODO), but I can imagine more could come.

Or am I seeing your solution wrong? :)

mglaman’s picture

I was thinking we could do something like...

form_error($form['commerce_discount_fields']['commerce_discount_offer'][LANGUAGE_NONE]['form']['commerce_percentage'], t('Percentage should be a positive number, excluding zero!'));

I haven't had the chance to debug the form structure to see if that has proper #parents structure, though. But that should be the "proper" way to set the error on the Field API element.

I also wonder if we could write a test for this, too. There is a Discount UI test, but it only works for fixed discount. Would be nice to have a Discount UI Percentage Validation test. Shows it invalidates with bad value, creates discount properly.

Alienpruts’s picture

I'll try the form_error and see if that works. New patch coming soon.

About tests : like the idea but I'm not really qualified to do those, but if anybody else would like to take a shot at those?

joelpittet’s picture

StatusFileSize
new2.9 KB

I'm willing to commit this without the UI tests. We can make that a follow-up and I don't mind writing them either.

Attached is an interdiff with nitpicks, could you apply that too with your improvements to the validation @Alienpruts?

michfuer’s picture

Status: Needs review » Reviewed & tested by the community

Looks good. Update function tested out. Percentage calculations look accurate. In the update function return text add a space like 'update! '

@Alienpruts you're correct that if more fields are added to the 'percentage' bundle, and those fields require validation beyond what the field type already provides, than commerce_discount_form_validate() will need to be updated.

joelpittet’s picture

Status: Reviewed & tested by the community » Fixed

Ok I've committed this as is (with the interdiff). @Alienpruts if you have better validation stuffs or any tweaks you can post them here or in a follow-up issue I don't mine.

Alienpruts’s picture

Agreed,

the validation seems to work for the necessary element (percentage discount value) anyway, no matter the function called. +1 for committing.

Tnx for the nitpicking fix, I really ought to take more care in my code styling. :)

torgospizza’s picture

Weirdly enough, I downloaded the latest dev of Discount, and it seems I was able to apply a discount even though the coupon error'd out on me, if the Discount was enabled but the Coupon was not. The discount would apply and show up in the cart summary pane, but in the coupon pane I'd get the error "Could not apply coupon".

Status: Fixed » Closed (fixed)

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