Problem/Motivation

When editing a shipment on an order that was placed by an anonymous customer clicking the "Recalculate shipping" button causes the shipping address to be blanked out. This bug was introduced in version 2.2, specifically #3242737: Shipping profile element doesn't work with recalculate on admin shipping form

Steps to reproduce

Have at least commerce_shipping version 2.2 installed
Create an order assigned to anonymous. Create a shipment for that order and save it. Go back to edit the shipment. Click the "Recalculate shipping" button. The shipping address disappears. Saving the shipment saves an empty address. Abandoning the changes and coming back to the shipment edit screen the address is present so the underlying profile is not being modified until saving the shipment.

Proposed resolution

Currently doing further debugging to determine the cause, though I suspect it's because the form is being rebuilt and for whatever reason the code path that sets an empty address is being triggered instead of reusing the existing profile like what happens when the form is initially loaded.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rhovland created an issue. See original summary.

rhovland’s picture

I've tracked this down to the following code block being removed.
https://git.drupalcode.org/project/commerce_shipping/-/commit/3f492dc441...

If I restore that then the regression goes away.

rhovland’s picture

The issue is that for whatever reason $address is empty even where there is a shipping profile already attached to the shipment. This happens when the order is owned by anonymous. Oddly this does not happen when the order is owned by a user, even if they have no shipping addresses in their address book and it's only the address stored on the shipment.

This is also a problem if a shipping profile is attached to the shipment during building of the form (via custom code) and the address is blown away by the recalculate button regardless of ownership of the order.

rhovland’s picture

Here's a patch that "fixes" the problem by not touching the profile if the address is NULL.

The real fix will be figuring out why $address is empty and fixing that.

tBKoT made their first commit to this issue’s fork.

tBKoT’s picture

This patch is only to check failing test

tBKoT’s picture

Status: Active » Needs review
tBKoT’s picture

jsacksick’s picture

Status: Needs review » Needs work

@tBKoT: Please update an existing test, no need to define a new one, let's just expand ShipmentAdminTest. Besides, the tests-only patch doesn't seem to apply.

tBKoT’s picture

Status: Needs work » Needs review
FileSize
1.73 KB
rhovland’s picture

After spending time in the debugger and testing different scenarios I'm pretty sure I know what is wrong and needs to be fixed.

Setting the selected_profile_id for anonymous users technically works but is the wrong approach here. We only need to trigger the code that updates the current shipment profile when the selected profile changes. Since an anonymous order will never have a choice of profiles to select there is no reason to update the profile.

What does need to happen is if the user clicks the edit button and changes the address. The shipment profile does need to be updated with the changes before rate recalculation or you won't see updated rates. This will not happen because that was what `if ($address !== NULL) {$shipment->getShippingProfile()->get('address')->setValue($address);}` was intended to do and invoking the profile selection path will wipe out those changes.

So the following code will update the shipment profile if an address has been entered or edited in the address form.
If the address edit form is not open ($address is NULL) then it moves on to the next check where it looks at the selected profile id. If there is a selected profile id it updates the shipment address using that. It will be NULL on anon orders and users with only one profile because there is no select_address form. There is no need to update the shipment profile in that case, because it is already current.

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
    $triggering_element = $form_state->getTriggeringElement();
    $recalculate = !empty($triggering_element['#recalculate']);
    if ($recalculate) {
      $form_state->set('recalculate_shipping', TRUE);
      /** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
      $shipment = $this->entity;
      $shipment->setTitle($form_state->getValue('title'));

      $base_form_key = ['shipping_profile', '0', 'profile'];
      $selected_profile_key = array_merge($base_form_key, ['select_address']);
      $selected_profile_id = $form_state->getValue($selected_profile_key);
      $current_profile_id = $shipment->getShippingProfile()->id();
      $address_key = array_merge($base_form_key, ['address', '0', 'address']);
      $address = $form_state->getValue($address_key);
      // If the address entry form is open, copy the address into the shipping profile so
      // rates can be returned. If a new address is being entered the shipment profile will
      // be emptied so no rates are returned.
      if ($address !== NULL || $selected_profile_id === '_new') {
        $shipment->getShippingProfile()->set('address', $address);
      }
      // If a new address is being entered, empty the shipment profile so no rates are returned.
      //elseif (!empty($selected_profile_id) && $selected_profile_id === '_new') {
      //  $shipment->getShippingProfile()->set('address', NULL);
      //}
      // If a different profile was selected, load it and use its address.
      elseif (!empty($selected_profile_id) && is_numeric($selected_profile_id)) {
        $profile_storage = $this->entityTypeManager->getStorage('profile');
        $selected_profile = $profile_storage->load($selected_profile_id);
        assert($selected_profile instanceof ProfileInterface);
        $shipment->getShippingProfile()->set('address', $selected_profile->get('address')->first()->toArray());
      }
      // Add the shipping items.
      $this->addShippingItems($form, $form_state);

      if (empty($form_state->getValue('package_type'))) {
        return;
      }
      $package_type = $this->packageTypeManager->createInstance($form_state->getValue('package_type'));
      $shipment->setPackageType($package_type);
    }
  }

Tested with the following scenarios:

  • ✓ New shipment for a new order with no existing shipping profiles
  • ✓ New shipment with one existing address book profile
  • ✓ New shipment with more than one existing address book profile
  • ✓ New shipment with more than one existing address book profile while adding a new address
  • ✓ Editing an existing shipment on an anonymous order
  • ✓ Editing an existing shipment on an anonymous order while changing the address
  • ✓ Editing an existing shipment with one existing address book profile
  • ✓ Editing an existing shipment with one existing address book profile while changing the address
  • ✓ Editing an existing shipment with one existing address book profile while adding a new address
  • ✓ Editing an existing shipment with more than one existing address book profile
  • ✓ Editing an existing shipment with more than one existing address book profile while switching the address
  • ✓ Editing an existing shipment with more than one existing address book profile while editing the selected address
  • ✓ Editing an existing shipment with more than one existing address book profile while adding a new address

Any scenarios I missed?

rhovland’s picture

Status: Needs review » Active

Oops I left in some commented out code in that sample. This part needs to be removed

      // If a new address is being entered, empty the shipment profile so no rates are returned.
      //elseif (!empty($selected_profile_id) && $selected_profile_id === '_new') {
      //  $shipment->getShippingProfile()->set('address', NULL);
      //}
tBKoT’s picture

Status: Active » Needs review

  • jsacksick committed c52ac18d on 8.x-2.x authored by tBKoT
    Issue #3350321: Recalculate shipping button blanks address when editing...
jsacksick’s picture

Status: Needs review » Fixed

Committed, thanks everyone!

Status: Fixed » Closed (fixed)

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