When setting up shipping I have a need to be able to change flat rate shipping based upon a list of countries or post codes in a country.

Using rules and working with 'Order address component comparison' unless I'm missing something you have to make a separate condition for each country/postcode when it would be much easier to include a list of values in one condition.

I've noticed a number of threads asking similar questions but I've found no published solution to this so I've written my own using hook_rules_condition_info_alter that adds 'is one of' and 'begins with one of'.

However it strikes me that it may be useful to others and in my opinion it would be worth altering the commerce_order.rules.inc file. What is the best way to suggest a change to the commerce core?

The only changes needed are to commerce_order.rules.inc and the changes are

Old code

function commerce_order_address_comparison_operator_options_list() {
  return array(
    'equals' => t('equals'),
    'begins with' => t('begins with'),
    'contains' => t('contains'),
  );
}

New code

function commerce_order_address_comparison_operator_options_list() {
  return array(
    'equals' => t('equals'),
    'begins with' => t('begins with'),
    'contains' => t('contains'),
    'is one of' => t('is one of'),
    'begins with one of' => t('begins with one of'),
  );
}

Old code

function commerce_order_rules_compare_address($order, $address_field, $component, $operator, $value) {
  list($field_name, $address_field_name) = explode('|', $address_field);

  // If we actually received a valid order...
  if (!empty($order)) {
    $wrapper = entity_metadata_wrapper('commerce_order', $order);

    // And if we can actually find the requested address data...
    if (!empty($wrapper->{$field_name}) && !empty($wrapper->{$field_name}->{$address_field_name})) {
      $address = $wrapper->{$field_name}->{$address_field_name}->value();

      // Make a comparison based on the operator.
      switch ($operator) {
        case 'equals':
          return $address[$component] == $value;
        case 'begins with':
          return strpos($address[$component], $value) === 0;
        case 'contains':
          return strpos($address[$component], $value) !== FALSE;
      }
    }
  }

  return FALSE;
}

New code

 function commerce_order_rules_compare_address($order, $address_field, $component, $operator, $value) {
  list($field_name, $address_field_name) = explode('|', $address_field);

  // If we actually received a valid order...
  if (!empty($order)) {
    $wrapper = entity_metadata_wrapper('commerce_order', $order);

    // And if we can actually find the requested address data...
    if (!empty($wrapper->{$field_name}) && !empty($wrapper->{$field_name}->{$address_field_name})) {
      $address = $wrapper->{$field_name}->{$address_field_name}->value();

      // Make a comparison based on the operator.
      switch ($operator) {
        case 'equals':
          return $address[$component] == $value;
        case 'begins with':
          return strpos($address[$component], $value) === 0;
        case 'contains':
          return strpos($address[$component], $value) !== FALSE;
        case 'is one of':
          $list = preg_split('/[\n\r]+/', $value);
          return array_search($address[$component], $list) !== FALSE;
        case 'begins with one of':
          $list = preg_split('/[\n\r]+/', $value);
          foreach ($list as $item) {
            if(stripos($address[$component], $item) !== FALSE) {
              return true;
            }
          } 
      }
    }
  }

  return FALSE;
}

Sorry it's not in the form of a patch but I'm not set up to produce them at the moment but hopefully the above will help someone else.

It only adds a couple off array elements and small switch statements but I've found the changes very helpful.

An example of my usage is I want to exclude free delivery to the UK post codes PO30 - PO41 so instead of entering multiple condition I can make one condition with 'begins with one of' and put the multiple values in it with each one separated with a new line.

CommentFileSizeAuthor
#1 commerce-order-1976480-1.patch1.15 KBDrMicky
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Ollie222’s picture

Issue summary: View changes

Change code tags to php tags

Ollie222’s picture

Issue summary: View changes

Add changes comment

Ollie222’s picture

Issue summary: View changes

An an example of use

DrMicky’s picture

Status: Active » Needs review
FileSize
1.15 KB

Hi

Thanks for this it looks like exactly what we need.

I can't do all that clever php stuff myself but I can make patches so I have created a patch against dev here ...

I tested it locally and it seems to work fine. Will move our dev project on to commerce 7.x-1.x and test it there now ...

DrMicky’s picture

Have now run the patch in #1 against 7.x-1.7 (because we don't want to use dev as the project is about to go UX).

Patch runs cleanly.

Seems to function pretty well although there could be issues (with the function not the code) where you are excluding, say, HS1-HS9 postcodes using 'begins with one of' 'HS1 ', 'HS2 ' (note not HS10 hence the space) etc. and a customer in HS1 0AB inputs their postcode as HS10AB.

Also worth documenting that each option needs to go on a new line.

I think this is a great improvement to the module and I wish we had it before we created bzillions of rule components for all the different eu countries!

Thanks Ollie222

Ollie222’s picture

Thanks for creating the patch Micky, I really should get around at looking how to create them.

One thing I will point out is that 'begins with one of' is case insensitive so as to allow for my use case scenarios of checking entered postcodes whereas all of the other options are case sensitive.

This suited my use case of checking countries and postcodes but it may not be that obvious to others and I'm not sure if it needs handling elsewhere.

You're also correct about the possible postcode issue.

This will always be difficult to handle as technically you could have B1 2DE and B12 3DE and if they were entered without the space and you were looking for postcodes beginning with B12 you would also get all of them beginning with B1 2 too.

You'd have to start looking at regular expressions to look at where the letters appear after the last number.

jsimonis’s picture

Going to see if this will work with the current version of Drupal Commerce. This would be extremely helpful for doing sales tax.

travismark’s picture

This patch worked for me also, Used 'is one of' because I need to include multiple countries in one condition.

jsimonis’s picture

I didn't use the patch, but did it manually since I am using a different version of the module. As far as I can see, it works for me. Having the "is one of" is a great option for doing sales tax. I thought I was going to have to manually add about 250 cities worth of conditions. This was soooo much help!

end user’s picture

Nice this was a huge time saver for Canadian taxes on shipping.

gokulnk’s picture

Status: Needs review » Reviewed & tested by the community

Reviewed. The code works like a charm. I think this should go to the core.

joecanti’s picture

Hi,

This code worked great. I've got nearly 2000 postcode outcodes added to 10 different delivery zones - which get selected depending on the customers postcode.

The only small problem is as mentioned in #3. Its probably confined to British postcodes, because we have an area code - BS (eg city of bristol) - followed by a number.

If someone enters BS15 - they'll get the postage options for BS15, and also BS1. There are 2 possible solutions as I see it - firstly to just add a message containing the postscodes so they can choose, or secondly, to split the postcode entry box into 2, so that an exact match can be applied to the first box.

The second would be preferable. how could we do that?

Anyway - the code works great, so +1 for getting it into core.

Cheers, Joe

rszrama’s picture

Version: 7.x-1.6 » 7.x-1.x-dev
Status: Reviewed & tested by the community » Fixed

Finally had a chance to check this patch. The "begins with one of" would have produced a false positive using the !== FALSE comparison, so I changed that to a === 0 (which with strpos() is the way to ensure the string starts with the desired string). Also, I liked the idea of case insensitive matching, which is what stripos() offers over strpos(), so I've made this consistent for all operators (instead of just "begins with one of") by using drupal_strtoupper() on both comparison strings ahead of the switch.

I don't think there's much we can do with postal codes that may or may not have a space in them. My recommendation would be some sort of checkout form customization to strip spaces out of the entered data if you depend on this sort of condition.

Commit: http://drupalcode.org/project/commerce.git/commitdiff/7fa6118

Ollie222’s picture

The only sensible way to get the postcode check to recognise the difference between BS1 and BS15 would be to use a regular expression for the checks instead of just a string comparison to make sure the next character is a letter or space and not a number.

It's possible to do but not as efficient particularly if you have a lot of postcodes.

Status: Fixed » Closed (fixed)

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

Anonymous’s picture

Issue summary: View changes

Correction of typo in code

Khalilou’s picture

thank patch work (thank rszrama)

obuck’s picture

The multiple values comparison isn't working for me. Excuse my confusion, but this patch was created 3 years ago -- do I still have to apply it manually or was it included in the current version?

I also have to use it for multiple zip code comparisons, for New York sales tax. The rule I have set up works to apply sales tax to an order if there is only one zip code in the Value, but stops working as soon as I have 2 or more zips, each on their own line, with or without commas.

I have 2 installations that I'm trying to get this working on. I don't know the standard procedures for setting it up in a sandbox or whatever, but I did set up Commerce Kickstarter locally and tested it there and got the same result.
This is how the Rule Condition looks:

Order:
commerce-line-item:order

ADDRESS
The address associated with this order whose component you want to compare.
Value:
Address

ADDRESS COMPONENT
The actual address component you want to compare. Common names of address components are given in parentheses.
Value:
Postal Code

OPERATOR
The comparison operator.
Begins With (avoids the issue of people entering 10 digit codes)

VALUE
The value to compare against the address component. Bear in mind that addresses using select lists for various components may use a value different from the option you select. For example, countries are selected by name, but the value is the two letter abbreviation. For comparisons with multiple possible values, place separate values on new lines. <- says it works!

works:
12469

doesn't work:
12469
12468

...let alone the full list of zips.

rszrama’s picture

It was committed in #10, but I'm not sure it provided the functionality you're looking for.

jsimonis’s picture

It's not going to work with what you're doing. With the setup you have ("begins with"), it's not an either/or type situation. So what they enter has to begin with all of those things you have on the list (hence it fails). What we're using is "is one of", which gives you that either/or situation. It works fine with that condition.

I don't know of any way to do it the way you're doing it. I think you're going to have to just make it very clear that you only want a 5 digit zipcode and not 10.

obuck’s picture

Despite the discouragement, I took another look at it, and it seems there is an option hiding at the bottom (I guess I didn't see it because it fell below my screen edge when I was at that section) for "begins with one of." It works.

Thanks for your responses, though, I don't always get responses in forums.

Using zip codes isn't 100% accurate. For one of the two Commerce sites I'm setting up now I found that Avalara does have a reasonably-priced option for the small number of transactions I'm expecting.

jsimonis’s picture

Oh good, glad you found an option that works. I do wish there were easier ways to do sales tax. We have to do it in California and we've just been using city names. Most of those I work with can't afford the connectors that do it for you.