The code in commerce_order_commerce_customer_profile_presave() checks whether values of a customer profile entity are equal to the original entity.
This behavior is used by the Commerce Addressbook module in order to assign an earlier entered customer profile to a new order.
The beauty of this is that it avoids creation of duplicate customer profiles in case the address info is always the same.

However a problem arises when a custom field has been added to the customer profile (e.g. VAT number).

The following code in the function checks for equality of the field data:

      elseif ($profile->{$field_name} != $unchanged_profile->{$field_name}) {
        $field_change = TRUE;
        break;
      }

However, upon testing this with a custom field, there is a difference between the 2 fields anyway:

  $profile->field_vat_number = array(
    und => array(
      0 => array(
        'value' => 'VAT #'
      ),
    ),
  );

vs.

  $unchanged_profile->field_vat_number = array(
    und => array(
      0 => array(
        'value' => 'VAT #',
        'format' => NULL,
        'safe_value' => 'VAT #'
      ),
    ),
  );

I.e. the extra array elements cause the profile to always be marked as different...

I suppose this isn't an easy problem to fix, and I haven't had the time to check how this could be achieved.
Hopefully someone has a better view on the problem and can come up with a solution.

Sven

Comments

rszrama’s picture

How did your data change between the two instances? Is it that on save the additional parameters are added into it but we don't have those bits of data when we submit the checkout form?

bojanz’s picture

StatusFileSize
new3.95 KB

The current code is just not good enough.
It has advanced comparison for addressfields (comparing added deltas, common columns), but for ordinary fields (which could just as easily have multiple deltas and columns) it just does a simple comparison.

Cases where it fails and causes a profile duplication:
1) When the "Use as shipping address" checkbox provided by Commerce Shipping is present on the form.
2) For any custom multivalue and / or multicolumn field (including other addressfields, such as here: #1361330: Customer profile duplication with more than one addressfield).
3) When a field is submitted empty. One entity has 'field_name' = array(), the other 'field_name' => array(LANGUAGE_NONE => array()), which fails the comparison.
4) The case from #0 (columns added by the system messing up the comparison)

Attaching a patch that makes the code much more bullet proof and fixes the problems listed above.

EDIT: Needs a bit of tweaking to account for removed values from custom multivalue fields. Will reroll after lunch.

it-cru’s picture

Title: Problem in commerce_order_commerce_customer_profile_presave equality check » The customer profile duplication code has numerous edge cases that cause profiles to be needlessly duplicated
Status: Active » Needs review

Hello together,

sounds a little bit like my problem (Drupal Commerce 7.x-1.2).

I try to save a profile loaded from an order in the hook_commerce_checkout_complete() to set their two fields with some external crm values. Everytime on commerce_customer_profile_save a new costomer profile revision is created in the database and so my added crm data will not be saved in the customer profile of the order. But the description of commerce_customer_profile_save() says that the profile is updated when a profile_id is present or missed I something in the $profile object to save it without a revision?


function <mymodule>_commerce_checkout_complete($order) {
  $profile_id = $order->commerce_customer_billing[LANGUAGE_NONE][0]['profile_id'];
  $profile = commerce_customer_profile_load($profile_id);

  $data = <fill data array with values>

  //fill empty profile fields with data
  $profile->field_customer_crm_id[LANGUAGE_NONE][]['value'] = $data['crm_id'];
  $profile->field_customer_crm_type[LANGUAGE_NONE][]['value'] = $data['crm_type'];

  $status = commerce_customer_profile_save($profile);

  if ($status == SAVED_NEW)
    dsm('NEW'); //dsm output always NEW
  elseif ($status == SAVED_UPDATED)
    dsm('UPDATE');
  else
    dsm($status);

}

Perhaps someone have a little hint or idea.

Greetz
Steffen

it-cru’s picture

Ok find another solution by myself: hook_commerce_customer_profile_presave($profile) seems to be my friend to complete my extra profile fields with the data from the external data source =)

But the problem is, that this will possible create new data in the external data base before the order is completed...

bojanz’s picture

StatusFileSize
new4.4 KB

@IT-Cru
Feel free to open a new issue.
I'm here focused on fixing a very specific function, anything else is unrelated.

Here's a new patch. Simpler than the last one, and covers the case when a value has been removed from a multivalue field.
Should fix all our problems :)

bojanz’s picture

StatusFileSize
new4.41 KB

Introduced a notice.
It's amazing how we need this code:

+      // Make sure that empty fields have a consistent structure.
+      if (empty($profile->{$field_name})) {
+        $profile->{$field_name}[$langcode] = array();
+      }
+      if (empty($original_profile->{$field_name})) {
+        $original_profile->{$field_name}[$langcode] = array();
+      }

Since empty fields can vary between $field_name => array() and $field_name => array($langcode => array()).
It's a proof of how bad Field API DX can actually be.

lisa.lite’s picture

commerce-7.x-1.2
commerce_addressbook-7.x-2.0-rc1
commerce_shipping-7.x-2.0-beta1

applied patch #6

when checking "Use billing address as shipping address" the Shipping information customer profile is still duplicated

jazzdrive3’s picture

I can also verify that profiles are duplicated when using the "Use billing address as shipping address" functionality of addressbook.

helior’s picture

Issue tags: +1.3 review

Tagging to make sure we review this for a Commerce 7.x-1.3 release.

helior’s picture

Status: Needs review » Reviewed & tested by the community

I also experienced duplicate profiles when using the "Use billing address as shipping address" feature on Commerce Shipping, but I think that's an unintentional flaw on the Commerce Shipping module. That checkbox value is saved along side the profile because of the nature in which it was implemented, but it is in fact just a setting that does not (and should not) persist on any profile. This patch is rock solid; I think there should be a follow-up issue on Commerce Shipping to rethink the way that feature is implemented.

bojanz’s picture

My patch should only compare the common columns if I remember it correctly. Shipping only adds that column to one of the addressfields.
I'll do a quick checkup tonight to see if I missed something.

EDIT: Confirmed that everything is fine. Even when using that checkbox, you will get one billing and one shipping profile, that's expected and this patch doesn't change that.

rszrama’s picture

Status: Reviewed & tested by the community » Fixed

Alrighty, read it through and can't find any fault with the new logic. Test bot says it's clean, Helior says it's good, my test went fine... committing!

And for reference, here's a discussion I kicked off on fixing the "copy address" functionality. The way it's implemented isn't good, as it's really just a UI element tacked onto the form... not some bit of data that should be contained inside a profile object or an address field value (especially since copying an "address" may actually include more than just the address field): #1410022: Improve copy address functionality - create a more generic solution

Status: Fixed » Closed (fixed)

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

mile23’s picture

Project page still links to this patch: http://drupal.org/project/commerce_addressbook

asauterChicago’s picture

Any updates on this? This issue still persists in the latest commerce. Also, none of the patches here work anymore since it's patches commerce_order, which has since been changed fairly significantly since the last patch 2 years ago.

bojanz’s picture

@sauterChicago
All fixes from this issue have been committed and are present in the last 7 or more releases.

tararowell’s picture

I am running 7.x-1.10 and I am still getting duplicated addressbook entries because of added fields on the customer profile form.
I am running Commerce Addressbook 7.x-2.0-rc7.

Thoughts?

jphelan’s picture

I'm also still getting duplicate address book entries for every order. Just test it after updating to Commerce Kickstart 7.x-2.19 and Commerce Addressbook 7.x-2.0-rc8. It's still duplicating both. Am I missing something?

WillsCreative’s picture

I'm receiving duplicates still as well.

Anonymous’s picture

Same here

dmsmidt’s picture

I thought this issue was still a problem, but in essence it seems fixed indeed.

My problem was caused by a module extending addressfield: addressfield_link.
A fix is here: https://www.drupal.org/node/2493505

Since that module borrows a lot from addressfield_phone, I guess that module is also problematic.

In general I would advice to check for problems in modules that add field types or extend them.
Remove fields from the profile and/or disable the extra modules and see if the problem persists