Hello,

I am getting duplicate billing addresses for a customer's account every time the profile is saved. It is my understanding that the system should "disable" previous revisions of the profile when an update is saved.

For a better picture (if you have installed this module for integration with Drupal Commerce) the following modules are used together to manage addresses.

Address Field (this module)
http://drupal.org/project/addressfield

This of course the Drupal Commerce Modules:
Drupal Commerce
http://drupal.org/project/commerce

It is important to note that commerce has several sub-modules most notably the customer module. The module has business logic to managing addresses (address field is a dependency). Address instances are appended to the customer profile entity in methods located in the commerce_customer.module file.

I'm leveraging a community module that extends the basic integration between addressfield and commerce entitled:
Commerce Address Book
http://drupal.org/project/commerce_addressbook

This module extends the basic integration and provides more business rules that don't exist in the standard commerce_customer module. Most notably it adds a management interface for the customer with the ability to create, edit, and delete addresses outside of the order process.

I believe the issue that causes duplicates is located here:

commerce_addressbook/includes/commerce_addressbook.user.inc
Line 30:

/**
 * Page callback for editing a customer profile.
 */
function commerce_addressbook_profile_options_edit($account, $customer_profile) {
  // Add the breadcrumb for the form's location.
  commerce_addressbook_set_breadcrumb($account, $customer_profile->type);

  // If the profile is referenced by an order, make sure it gets duplicated.
  $profile = clone($customer_profile);
  if (!commerce_customer_profile_can_delete($customer_profile)) {
    $profile->previous_id = $profile->profile_id;
    unset($profile->profile_id);
    unset($profile->revision_id);
    $profile->is_new = TRUE;
  }
  module_load_include('inc', 'commerce_customer', 'includes/commerce_customer_profile.forms');
  return drupal_get_form('commerce_addressbook_customer_profile_form', $profile);
}

This method calls "commerce_customer_profile_can_delete($profile) which states:
commerce/modules/commerce_customer.module

Online 628:
// Return FALSE if the given profile does not have an ID; it need not be
// deleted, which is functionally equivalent to cannot be deleted as far as
// code depending on this function is concerned.

And a little later in the method
Online: 635
// If any module implementing hook_commerce_customer_profile_can_delete()
// returns FALSE the customer profile cannot be deleted. Return TRUE if none
// return FALSE.

So a quick search of my source code reveals the following places where modules influence if a profile can be deleted:

commerce/modules/commerce_order.module
Online: 458
function commerce_order_commerce_customer_profile_can_delete($profile)

The function comments say it all:


function commerce_order_commerce_customer_profile_can_delete($profile) {
  // Look for any non-cart order with a reference field targeting the profile.
  foreach (commerce_info_fields('commerce_customer_profile_reference') as $field_name => $field) {
    // Use EntityFieldQuery to look for orders referencing this customer profile
    // and do not allow the delete to occur if one exists.
    $query = new EntityFieldQuery();

    $query
      ->entityCondition('entity_type', 'commerce_order', '=')
      ->fieldCondition($field_name, 'profile_id', $profile->profile_id, '=')
      ->count();

    // Add a condition on the order status if there are cart order statuses.
    $statuses = array_keys(commerce_order_statuses(array('cart' => TRUE)));

    if (!empty($statuses)) {
      $query->propertyCondition('status', $statuses, 'NOT IN');
    }

    // If the profile includes an order context property, we know this was added
    // by the Order module as an order ID to skip in the deletion query.
    if (!empty($profile->entity_context['entity_id']) && $profile->entity_context['entity_type'] == 'commerce_order') {
      $query->propertyCondition('order_id', $profile->entity_context['entity_id'], '!=');
    }

    if ($query->execute() > 0) {
      return FALSE;
    }
  }

  return TRUE;
}

So, if an order in the past used the address then modifying the address for a current or future order will result in the system duplicating the address with the changed values. Old addresses are not deleted, period.

What I'm assuming is not working is that the "expired" addresses should be hidden from the user and only the "new" address revision should be visible.

Retracing back to ye old commerce_addressbook module the submission method is suppose to accomplish this.

commerce_addressbook/includes/commerce_addressbook.user.inc
Online: 45

/**
 * Submit handler for commerce_addressbook_customer_profile_form.
 */
function commerce_addressbook_customer_profile_form_submit($form, &$form_state) {
  $profile = $form_state['customer_profile'];

  // The profile has been edited and duplicated.
  // Disable the previous one to prevent it from showing up in listings.
  if (!empty($profile->previous_id)) {
    $old_profile = commerce_customer_profile_load($profile->previous_id);
    $old_profile->status = 0;
    commerce_customer_profile_save($old_profile);

    // If the old profile was the default, then we need to set the new one
    // as the default.
    $default_profile_id = commerce_addressbook_get_default_profile_id($profile->uid, $profile->type);
    if ($old_profile->profile_id == $default_profile_id) {
      commerce_addressbook_set_default_profile($profile);
    }
  }

  // Set the profile as default if it has just been added, and the user has no
  // other active profiles of this type.
  if (!empty($profile->_is_new)) {
    $query = new EntityFieldQuery;
    $query->entityCondition('entity_type', 'commerce_customer_profile')
          ->entityCondition('bundle', $profile->type)
          ->propertyCondition('uid', $profile->uid)
          ->propertyCondition('status', 1)
          ->count();
    $result = $query->execute();

    if ($result < 2) {
      commerce_addressbook_set_default_profile($profile);
    }
  }

  $form_state['redirect'] = 'user/' . $profile->uid . '/addressbook/' . $profile->type;
}

Online 52 of the method the current "target" profile has the status set to 0. Meaning it is disabled and should not be visible any longer. However it still appears both in the management indexes and during order checkout.

:(

Cross Posted at:

http://drupal.org/node/1940156

http://drupal.org/node/1185996

http://drupal.org/node/1794064#comment-7166112

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rszrama’s picture

Category: bug » support
Status: Active » Closed (works as designed)

Sorry, I saw this in the Addressbook queue first, but this is working as intended. We don't do anything with what you're calling "expired" customer profiles, but if you want to you can develop a system to simply disable them if the customer orders with a different address. Or look into http://drupal.org/project/commerce_single_address.

rszrama’s picture

See also: http://www.drupalcommerce.org/node/297

And I don't suppose you can update your discussion with some of this info? : )

emptyvoid’s picture

Thanks for the information rszama I'll investigate further.

I think the route of changing old profiles to status=0 may be the route I want to go as long as disabling the profile doesn't hide it from the order details where it was used. I'll also need to review the business logic that sets a profile as the "default" as once I disable a profile I'll need to also set the new one as the default.

I"ll have to continue conducting my research.

rszrama’s picture

Great - afaik, disabling it won't remove it from appearing in existing customer profile reference field displays. It works more like the product entity type's "active vs. disabled" than the node entity type's "published vs. unpublished."

ashzade’s picture

Have you had any luck with this? I have the exact same problem. Can rules be used to set the statuses to inactive when the profiles are cloned?

ashzade’s picture

Issue summary: View changes

update to track post discussions at the different module projects.

jasonglisson’s picture

Been a while since anyone has commented on this. Just wanted to mention that we successful implemented a workaround for this issue on one of our commerce websites. We're using AddressBook and Commerce Admin Order Advanced to create orders. The main issue we were having was giving an administrator an easy way for them to search for a customer. The Commerce Admin Order Advanced module worked great, but when they needed to pick an address for them, there would be dozens of the same address in the system.

Obviously the profiles are supposed to duplicate, but having the same address referenced over and over again in the UI wasn't a good user experience for us.

The module gives you the option of creating a new profile, or referencing one. So we simply said "If the profile is being referenced by an ID that already exist, when the new one is created, set it to deactivated".


  // Load the current shipping profile
  $profile_dedup = commerce_customer_profile_load($form_state['values']['shipping_profile']);

  // If the profile is set and has an ID
  if(isset($profile_dedup->profile_id)) {
  	
  	// Load the referenced customer profile into the order
  	$profile = commerce_customer_profile_load($profile_dedup->profile_id);

  	// Set the status to deactivated so the shipping profile doesn't show up in the select box
	$profile->status = 0;
	
	// Save the profile as deactivated
	commerce_customer_profile_save($profile);	 
  } 

I hope that helps someone in the future. It took us a while to find a good solution for this.

BYUStudies’s picture

@tk421jag Thanks for the comment and for recommending Commerce Admin Order Advanced. Can you tell me where you used the code snippet? Did you put it in a custom module?

JoshIdeas’s picture

I too ran into this issue while developing a customer interface for my clients to quickly choose from previous addresses related to a user. I discovered that one small change to an order even though you didn't touch the address sections, it would insert rather then update a record.

I eventually hacked Commerce code to do what I want - this isn't pretty and has obvious issues that come along with it. You can read what I did here.

@rszrama, I hope in the near future this is something that you can enable for customization rather then it being a forced feature.

samberry’s picture

I came across this issue when deleting addressbook, I decided to fix this by removing these lines in the patch attached. This stops the profile id from being emptied when saving an address, which in turn prevents the status from being set to 0.

Thanks,
Sam