Once a shipping method has been applied to an order, going back and changing the item quantity in your cart does not update the shipping total.

The patch proposes to create an event subscriber that reacts to order item quantities being changed and repacks a shipment if needed.

Original Issue:
The original issue addressed several problems at once which have been broken down in comment #41

Dear,

I am facing an issue where I am in my cart (/cart) and in my cart I have for example 5 items of 10 euros.

I have a Shipping method that adds 10euros when I have a total order price of below 200 euros.

Now when I continue the cart step, fill in my shipping information and then I realise, damn I forgot or I want to add items.

I return to the cart for example and add another 50items of the same item in my cart, I press update my cart and my page refreshes and I see the now order total.

However (The problem) I still see a shipping cost of 10euro even when my order total is already way beyond 200euros.

Can someone tell me this is by design or is this a bug ?

I already tried to figure it out in code, but so far no luck. Any pointers would be appreciated.

Kind regards.

CommentFileSizeAuthor
#90 interdiff_88-90.txt619 bytesjsacksick
#90 2957513-90.patch22.86 KBjsacksick
#88 interdiff_85-88.txt547 bytesjsacksick
#88 2957513-88.patch22.79 KBjsacksick
#85 interdiff_82-84.txt5.91 KBjsacksick
#85 2957513-84.patch22.74 KBjsacksick
#82 2957513-82.patch21.01 KBjsacksick
#79 interdiff_74-79.txt1.89 KBvalic
#79 2957513-79.patch20.62 KBvalic
#74 2957513-74.patch20.55 KBmglaman
#72 2957513-72.patch21.04 KBmglaman
#72 interdiff-2957513-72-67.txt8.82 KBmglaman
#67 interdiff_61-67.txt3.81 KBvalic
#67 2957513-67.patch20.46 KBvalic
#61 interdiff_57-61.txt2.53 KBvalic
#61 2957513-61.patch20.59 KBvalic
#57 2957513-57.patch20.36 KBvalic
#55 2957513-55.patch13.48 KBagoradesign
#54 2957513-54.patch13.54 KBagoradesign
#50 2957513-49.patch20.62 KBmglaman
#38 updateCartShipping.png18.09 KBNils Loewen
#37 interdiff.2957513.36-37.txt5.89 KBNils Loewen
#37 2957513-37.NEW-TEST-AND-PATCH-28.patch9.81 KBNils Loewen
#36 2957513-36.TEST-ONLY.patch3.92 KBNils Loewen
#36 2957513-36.commerce_shipping.Shipping-not-recalculated.patch3.92 KBNils Loewen
#30 interdiff-FAIL.2957513.28-30.txt4.73 KBNils Loewen
#30 2957513-30-FAIL.commerce_shipping.Shipping-not-recalculated.patch9.82 KBNils Loewen
#30 interdiff-PASS.2957513.28-30.txt3.92 KBNils Loewen
#30 2957513-30-PASS.commerce_shipping.Shipping-not-recalculated.patch9.81 KBNils Loewen
#29 2957513-28.patch5.81 KBsmccabe
#26 interdiff-2957513-21-26.txt622 bytesagoradesign
#26 2957513-26.patch5.89 KBagoradesign
#21 2957513-21-shipping-recalculation.patch5.84 KBagoradesign
#21 interdiff-2957513-18-21.txt642 bytesagoradesign
#18 2957513-18-shipping-recalculation-beta4.patch6.37 KBGoZ
#18 2957513-18-shipping-recalculation.patch6.35 KBGoZ
#16 interdiff-2957513-10-16.txt4.46 KBGoZ
#16 2957513-16-shipping-recalculation-beta4.patch6.35 KBGoZ
#16 2957513-16-shipping-recalculation.patch6.33 KBGoZ
#14 interdiff-2957513-10-14.txt4.46 KBGoZ
#14 2957513-14-shipping-recalculation-beta4.patch6.35 KBGoZ
#14 2957513-14-shipping-recalculation.patch6.33 KBGoZ
#10 2957513-10-shipping-recalculation.patch1.61 KBGoZ
#10 2957513-10-shipping-recalculation-beta4.patch1.63 KBGoZ
#9 2957513-8-shipping-recalculation-beta4.patch1.59 KBGoZ
#7 2957513-5-shipping-recalculation-beta4.patch1.62 KBGoZ
#6 2957513-5-shipping-recalculation.patch1.57 KBGoZ
#4 2957513-3-shipping-recalculation.patch1.62 KBGoZ
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mschudders created an issue. See original summary.

Mschudders’s picture

Issue summary: View changes
GoZ’s picture

Category: Support request » Bug report
Status: Active » Needs review

This is not implemented yet.

Here is a patch that recalculate shipping rates. The only limitation for the moment is that in case of multiple rates, we don't know which one was used, so we take the first one.

To be able to deal with many rates recalculation rates, some huge efforts should be done

GoZ’s picture

Status: Needs review » Needs work

The last submitted patch, 4: 2957513-3-shipping-recalculation.patch, failed testing. View results

GoZ’s picture

Status: Needs work » Needs review
FileSize
1.57 KB

A line has been removed, here is the fixed patch

GoZ’s picture

And here is a patch for beta4 version of module. Do not use this for development.

The last submitted patch, 6: 2957513-5-shipping-recalculation.patch, failed testing. View results

GoZ’s picture

Fix #7 for beta-4 version which embed code from -dev.
DO NOT use this for development.

GoZ’s picture

Here is a fix for both which occur when shipment has no method yet.

DO NOT use -beta4 for development.

webadpro’s picture

I'm actually facing the same issue here, and I have a feeling its rather something with the shouldRepack method. Sometimes it gets executed sometimes it doesn't. Maybe a flag should be set if only items quantity has changed. This patch seems to work half of the time. Can you test the following: Add a product, go through the checkout process until you reach the review page... then go in the cart and edit the quantity and checkout once again. On my end, the shipping cost never gets reCalculated and I think its the way shouldRepack is coded.

GoZ’s picture

@webadpro Your steps looks like what is described in main post and is the same as my tests, and shouldRepack return TRUE as expected. Can you provide more information ? Do you try with patch 2957513-10-shipping-recalculation.patch in #10 ?

webadpro’s picture

@GoZ, it seems if you are in the last step right before the payment, then the shouldRepack always returns false which doesn't make it recalculate. And yes I did applied your beta4 patch.

GoZ’s picture

Right, there is something about this case in comments. When we are on cart, previous steps are both null and we cannot know if quantity has changed.

In ShipmentOrderProcessor::shouldRepack():

    // Ideally repacking would happen only if the order items changed.
    // However, it is not possible to detect order item quantity changes,
    // because the order items are saved before the order itself.
    // Therefore, repacking runs on every refresh, but as a minimal
    // optimization, this processor ignores refreshes caused by moving
    // through checkout, unless an order item was added/removed along the way.
    if (isset($order->original) && $order->hasField('checkout_step')) {
      $previous_step = $order->original->checkout_step->value;
      $current_step = $order->checkout_step->value;
      $previous_order_item_ids = array_map(function ($value) {
        return $value['target_id'];
      }, $order->original->get('order_items')->getValue());
      $current_order_item_ids = array_map(function ($value) {
        return $value['target_id'];
      }, $order->get('order_items')->getValue());
      if ($previous_step != $current_step && $previous_order_item_ids == $current_order_item_ids) {
        return FALSE;
      }
    }

I suggest to add a flag when quantity is updated so we know we need repack. See patch.

DO NOT use -beta4 for development.

The last submitted patch, 14: 2957513-14-shipping-recalculation.patch, failed testing. View results

GoZ’s picture

The last submitted patch, 16: 2957513-16-shipping-recalculation.patch, failed testing. View results

GoZ’s picture

Fix and relaunch tests

DO NOT use -beta4 for development.

The last submitted patch, 18: 2957513-18-shipping-recalculation.patch, failed testing. View results

The last submitted patch, 18: 2957513-18-shipping-recalculation.patch, failed testing. View results

agoradesign’s picture

There was a (quite hidden) typo in #18, which you can see in the failed test results. Here's the fixed version

Status: Needs review » Needs work

The last submitted patch, 21: 2957513-21-shipping-recalculation.patch, failed testing. View results

MrPaulDriver’s picture

Would this be compatible with the recent Beta 5 release or only dev?

agoradesign’s picture

I have applied the patch against beta5 successfully

MrPaulDriver’s picture

Thank you, applied cleanly for me too

agoradesign’s picture

Status: Needs work » Needs review
FileSize
5.89 KB
622 bytes

fixing the CS violation

FiNeX’s picture

Hi, the bug is still present and the patch doesn't work on current -dev version.

Nils Loewen’s picture

Status: Needs review » Needs work

Changed to 'Needs Work' based on FiNeX's comment.

smccabe’s picture

Status: Needs work » Needs review
FileSize
5.81 KB

rerolled against latest -dev

Nils Loewen’s picture

This patch successfully solves the problem of updating the shipping amount.
The FAIL patch has the EventSubscriber commented out in the services.yml. This should be equivalent to running the new test the clean 8.x-2.x-dev branch.

Status: Needs review » Needs work
Nils Loewen’s picture

Status: Needs work » Needs review
quietone’s picture

Status: Needs review » Needs work

This patch successfully solves the problem of updating the shipping amount.

What did you do to fix that problem? The pass interdiff contains only a new test.

The fail patch with commented out code is unexpected. And it is a service so there is code in the fail patch not related to the failure. Is the test sufficient to demonstrate the failure? If so, can we have a patch with just that?

FiNeX’s picture

Hi, the "shipping rates" widget works fine, but the cart summary still display the previous shipping rate.

Customers still needs to go to the review page in order to view the correct total.

Now that https://www.drupal.org/node/2710999 has been solved (panes now should support ajax refreshing) the behaviour should be fixed.

Until then the shipping module is unusable because users doesn't see correct values :-(

Nils Loewen’s picture

@FiNeX

Could you send a screenshot of the error you are seeing? The test I wrote focuses on the /cart page and the summary that is shown when you change quantities and 'Update Cart'.

Perhaps we are addressing different errors?

Nils Loewen’s picture

Nils Loewen’s picture

@quiteone
I have not done anything to fix this issue, the solution was already in patch 28 and previous. I wrote a test to try prove that the issue is fixed.

Are these patches more helpful?

Nils Loewen’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
18.09 KB

@FiNeX

This is the page I was focusing on: /cart

rubenjara’s picture

This correction is necessary, customers get confused when they return and add more products to the shopping cart and the shipping method is not updated.

FiNeX’s picture

I've recorded a short video which clearly show the bug. Using commerce -dev, shipping -dev and your latest patch (without your patch nothing change): https://www.youtube.com/watch?v=8ZnaxSLMoQw

Nils Loewen’s picture

Title: Shipping not recalculated » 'Update Cart' does not recalculate shipping total
Issue summary: View changes
Related issues: +#2937403: Allow cart to have shipping selection

Thank you for the video @FiNeX.

You have found a rather complex set of problems. Let me try separate each issue.

There are three problems that you pointed out:

1. The first time the cart page (/cart) is loaded, the shipping total is not displayed.

Explanation: This is because the shipping method has not been applied to the order. This is the default behavior because there are many shipping types than cannot be calculated until the shipping address is known.

You raise a good point that when a 'Flat Rate' or 'Flat Rate Per Item' shipping method is selected, we should not need to wait to see the shipping address.

Solution: Apply shipping method to order when 'Add to Cart' is clicked, if applicable.
I have created this new issue to make this automatic: #3032265: Default shipping method for Order based on conditions
Also, this existing issue would would resolve this problem and problem 2a in the Cart form.#2937403: Allow cart to have shipping selection

2a. The first time the 'Order Information' page (/checkout/{order_id}/order_information) is loaded, the shipping total is not displayed in the 'Order Summary'.

Explanation: This is because the shipping method has not been applied to the order.

Solution: If the shipping method is applied to the order before the 'Order Information' page loads, this problem will be solved. The solution is connected to the new issue created for problem 1.

2b. The shipping method radiobutton is listed within the 'Shipping Information' container, but the shipping total is not updated/listed in the Order Summary.

Explanation: This is because the shipping total is not recalculated until 'Continue to review' form submit button is clicked.

Solution: #2898118: Update the order summary via AJAX when a shipping rate is selected

3. When you went back to your cart and updated the Item Quantity in your order, the new total price should have set failed your condition of shipping method 'Flat Rate' only applies to orders LESS THAN 80€.

3a. On /cart page, 'Update Cart' does not recalculate shipping total.

Solution: Patch 36 and previous address this problem. This issue should be limited to solving only this issue. I will update the description.

3b. On /cart page, 'Update Cart' does not re-assess shipping method conditions.

Solution: New Issue: Assess shipping method conditions when 'Update Order' is clicked.

FiNeX’s picture

Issue summary: View changes

Thanks @nlz, great analysis :-)

markdc’s picture

To avoid customers from seeing an outdated shipping cost in their cart, I've applied the patch from this related issue which does exactly what it says.

rubenjara’s picture

#43 I think it is the right solution.

mglaman’s picture

Title: 'Update Cart' does not recalculate shipping total » Order item modifications does not recalculate shipping total

This needs to be reviewed alongside #2994345: Remove shipping when cart is emptied. This would technically cause duplicate efforts. And we cannot always assume the cart module is available.

We have two problems:

- Cart manipulation
- Orders without cart module having order items updated.

mglaman’s picture

In #2994345: Remove shipping when cart is emptied I am proposing a new flag that can be set on orders to force repacking

+  public function onCartItemUpdate(CartOrderItemUpdateEvent $event) {
+    $cart = $event->getCart();
+    $cart->setData('shipping_force_repacking', TRUE);
+  }

This addresses my concern of duplicate processing in #45. Possibly.

I believe a flag on the order is better than a flag on the processor

+   * Flag shipments needs to be repacked.
+   *
+   * @var bool
+   */
+  protected $needsRepack = FALSE;
+

An order item save operation should correlate to a leading order save. So hopefully we can set the flag in this event subscriber and have it persist. Tests will tell.

mglaman’s picture

Status: Needs review » Postponed

Postponing for #2994345: Remove shipping when cart is emptied. Both are trying to solve the same problem. This issue has a patch which fixes recalculation. I think we should work on the other issue first, and then we can use this to ensure recalculation is available.

mglaman’s picture

Status: Postponed » Active

Discussed with bojanz. We will handle order item CRUD in this issue for updating shipments

GoZ’s picture

On #47 comment, the issue reference #2994345: Remove shipping when cart is emptied change subject. Talking about empty cart make more sense than removing shipment each time cart is refreshed.

Reading #47 the first time made me think shipping we'll be removed each time cart is updated. That's not the case, and it's fine !

mglaman’s picture

Status: Active » Needs review
FileSize
20.62 KB

Here is a rerolled patch which takes @nlz's work in #37 for \Drupal\commerce_shipping\Entity\Shipment::populateFromProposedShipment to recalculate costs.

It also takes work from my initial patches in #2994345: Remove shipping when cart is emptied for tests and flagging an order to be repacked.

nno’s picture

Besides being confusing without the patch it is possible to complete the checkout with completely wrong shipping amount.

Steps to reproduce:
- Create shipping method with "Flat rate per item" plugin.
- Add product to cart and go to checkout
- Fill in Shipping information and click "Recalculate shipping"
- Go back to cart and change item quantity
- Complete checkout

With current -dev version of the module and patch from #50 everything works great.

Thank you!

*DO NOT use patch from #50 with -beta6.

markdc’s picture

Status: Needs review » Needs work

Applied patch #50 to the latest dev. It needs work.

When modifying the cart, the shipping rate does not update on the cart form. Fine.

But when proceeding to the Order Information step, the shipping rate in the Shipping Information pane is updated, while the old rate continues to be displayed in the Order Checkout Summary of the sidebar. Only when clicking Continue to Review does the Order Checkout Summary get the new rate.

If a customer sees this discrepancy on the Order Information step, he is likely to:

  1. click the recalculate shipping
  2. refresh the page
  3. click the recalculate shipping again
  4. think there is something wrong with the shop
  5. hesitate to click on to the next step
  6. get annoyed
  7. think of contacting the store
  8. give up and maybe try again later

This is a UX no-go.

agoradesign’s picture

Reroll patch from #49 against beta8 (without the kernel test, as this is WIP anyway and -dev already has some changes inside).. just trying to get this work with beta8

agoradesign’s picture

FileSize
13.54 KB

sorry, here it is

agoradesign’s picture

FileSize
13.48 KB

next try

FiNeX’s picture

Thanks for the update @agoradesign

valic’s picture

Attaching a new patch against the latest dev with some additional changes.
We could cover also updating properly shipping amounts on a cart and do the recalculation of these in the early order processor.

Added tests for that case also. So now should be possible to have:
Add to cart -> order information -> choose shipping -> review -> cart -> update items -> recalculate and apply proper amount on cart

Note that when the shipping method needs to be changed, it is selected as they are sorted by amount. So with the lowest amount should be automatically chosen on the cart. And displayed proper shipping adjustment amount

The test is covering three scenarios for cart/checkout
1. Update order -> the shipping methods/rates stay the same
2. Update order -> shipping rates / methods needs update
3. Update order -> clear shipments while there are not available rates.

valic’s picture

Status: Needs work » Needs review

About placement of those, think it is viable to have an additional method in \Drupal\commerce_shipping\EarlyOrderProcessor which handles these cases (\Drupal\commerce_shipping\EarlyOrderProcessor::recalculateShippingRates)

Please test it.

The last submitted patch, 55: 2957513-55.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 57: 2957513-57.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

valic’s picture

Some coding standards cleanups.

Drupal\Tests\commerce_shipping\FunctionalJavascript\CheckoutPaneTest is failing on all tests, regardless of this patch.

But this one is failing seems only with a patch here Drupal\Tests\commerce_shipping\Kernel\ShipmentManagerTest

mglaman’s picture

Status: Needs review » Needs work

The last submitted patch, 61: 2957513-61.patch, failed testing. View results

mglaman’s picture

It looks like when we reset the shipments and pick the shipping method we don't ensure it is translated.

mglaman’s picture

  1. +++ b/src/EarlyOrderProcessor.php
    @@ -33,6 +33,11 @@ class EarlyOrderProcessor implements OrderProcessorInterface {
    +  protected $shippingManager;
    
    @@ -40,10 +45,13 @@ class EarlyOrderProcessor implements OrderProcessorInterface {
    +   * @param \Drupal\commerce_shipping\ShipmentManagerInterface $shipping_manager
    +   *   The shipping manager.
    ...
    +  public function __construct(EntityTypeManagerInterface $entity_type_manager, ShippingOrderManagerInterface $shipping_order_manager, ShipmentManagerInterface $shipping_manager) {
    ...
    +    $this->shippingManager = $shipping_manager;
    

    s/shippingManager/shipmentManager

  2. +++ b/src/EarlyOrderProcessor.php
    @@ -66,13 +74,69 @@ class EarlyOrderProcessor implements OrderProcessorInterface {
    +          $shipment->setShippingMethodId($shipping_rate->getShippingMethodId());
    +          $shipment->setAmount($shipping_rate->getAmount());
    +          $shipment->setOriginalAmount($shipping_rate->getAmount());
    +          $shipment->setShippingService($shipping_rate->getService()->getId());
    

    This is the same logic as

    public function selectRate(ShipmentInterface $shipment, ShippingRate $rate) {
    

    We should get the shipment method plugin and use selectRate on the given rate for the shipment.

  3. +++ b/src/EarlyOrderProcessor.php
    @@ -66,13 +74,69 @@ class EarlyOrderProcessor implements OrderProcessorInterface {
    +        // Assign new shipping rate only if existing is not among
    +        // listed available rates for shipment.
    +        if (!isset($shipment_rates[$shipping_rate])) {
    

    If it is set, shouldn't we still run selectRate on the shipping method plugin.

    So we get the rate, then

    $shipping_method = $shipping_method_storage->load($rate->getShippingMethodId());
    $shipping_method_plugin = $shipping_method->getPlugin();
    $shipping_method_plugin->selectRate($shipment, $rate);
    
valic’s picture

We should replace it with a proper select rate when does not exist.
But when rates exist, we should not alter anything. They are valid.

Funny thing is, that during tests which fail: ShipmentManagerTest,
it's never triggered on \Drupal\commerce_shipping\EventSubscriber\OrderItemSubscriber
$order->setData('shipping_force_repacking', TRUE);

So any underlying logic which lies on that, it is not executed during those tests, \Drupal\commerce_shipping\EarlyOrderProcessor::recalculateShippingRates then returns everything as they should be without the patch.

The crazy thing is If I remove that event subscriber \Drupal\commerce_shipping\EventSubscriber\OrderItemSubscriber then tests pass.

Even during test condition to set $order->setData('shipping_force_repacking', TRUE);
is never executed

valic’s picture

Status: Needs work » Needs review
FileSize
20.46 KB
3.81 KB

New patch.

Replaced as per #65 with selectRate, seems cleaner.

About failing tests, it is related to container states or something during tests that are executed.
When removed shipment order manager from an event subscriber, and using from #55 method to determine if the order is shippable, all tests should be green

valic’s picture

And additional comment to address one of the issues in the description

However (The problem) I still see a shipping cost of 10euro even when my order total is already way beyond 200euros.

Basing shipping methods condition on Order Total Price condition is not going to work properly in a lot of cases.
Because shipping adjustment amount is mostly included inside Order total price when that condition is executed.

Best would be to make a fork of Order total price condition without shipping adjustments included, or basing condition on Order Subtotal amount.

Or the best scenario maybe would be to extended Order Total Price in commerce core with the ability to include/exclude specific adjustments from the calculation

mglaman’s picture

Best would be to make a fork of Order total price condition without shipping adjustments included, or basing condition on Order Subtotal amount.

Or the best scenario maybe would be to extended Order Total Price in commerce core with the ability to include/exclude specific adjustments from the calculation

That's being worked on here #2938729: Condition based on order subtotal instead of order total

agoradesign’s picture

"ability to include/exclude specific adjustments from the calculation" sounds better than just picking subtotal -> when I calculate shipping costs based on a total amount, obviously I do not want the previously calculated shipping costs included, but on the other hand I still want promotions to be applied

mglaman’s picture

Assigned: Unassigned » mglaman

Going to build off of #67. For the order total comparison, I would love some effort on the core Commerce module issue.

  1. +++ b/src/Entity/Shipment.php
    @@ -104,7 +105,15 @@ class Shipment extends ContentEntityBase implements ShipmentInterface {
    +    if ($this->getShippingMethod() !== NULL && !$original_items->equals($this->get('items'))) {
    +      $rates = $this->getShippingMethod()->getPlugin()->calculateRates($this);
    

    I'm going to throw some debugging here and test this. I think this will always be false (which is probably fine.) The FieldItemList::equals does a strict check at first, which will fail if we didn't persist the same objects (I doubt we do.)

    It then later does a loose check against objects which might pass.

  2. +++ b/src/EventSubscriber/OrderItemSubscriber.php
    @@ -0,0 +1,60 @@
    +    if ($order !== NULL && $this->isOrderShippable($order) && $order_item->getQuantity() !== $order_item->original->getQuantity()) {
    +      $order->setData('shipping_force_repacking', TRUE);
    +    }
    

    Should we just always force recalculation if the order item updated? in 90% of use cases it will update because of the quantity. The other 10% would be for custom fields modified somehow, later. Might prevent follow ups to this line of code.

  3. +++ b/src/EventSubscriber/OrderItemSubscriber.php
    @@ -0,0 +1,60 @@
    +  private function isOrderShippable(OrderInterface $order) {
    +    return $order->hasField('shipments') && !$order->get('shipments')->isEmpty();
    +  }
    

    Weird that we have to provide this instead of using the shipping order manager. But I have seen goofier things in test state.

    EDIT I think it is due to the fact this has completely different logic. \Drupal\commerce_shipping\ShippingOrderManager::isShippable checks each purchasable entity whereas this just checks if shipments have been calculated yet.

mglaman’s picture

Assigned: mglaman » Unassigned
FileSize
8.82 KB
21.04 KB

Okay, here is an update. Also did some discussion with @valic. We renamed the order data key to shipping_force_refresh because it forces a repack and recalculation. It was also moved to a constant.

Also: interdiff is kind of wrong, had it as FORCE_PROCESSING not FORCE_REFRESH.

mglaman’s picture

+++ b/src/Entity/Shipment.php
@@ -90,6 +90,7 @@ class Shipment extends ContentEntityBase implements ShipmentInterface {
+    $original_items = clone $this->get('items');

This isn't used anymore. Forgot to delete.

mglaman’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
20.55 KB

I have pinged a few folks in Slack to review the patch. Here's a patch with #73 addressed. I'll be committing this within the next day unless someone objects.

agoradesign’s picture

I have some questions and proposals, upon reading the patch (not have tested it):

+  public function onOrderItemUpdate(OrderItemEvent $order_item_event) {
+    $order_item = $order_item_event->getOrderItem();
+    $order = $order_item->getOrder();
+    if ($order !== NULL && $this->orderHasShipments($order) && $order_item->getQuantity() !== $order_item->original->getQuantity()) {
+      $order->setData(EarlyOrderProcessor::FORCE_REFRESH, TRUE);
+    }
+  }

I'm not sure, if asking if the order has shipments is right here (on item delete it makes sense of course). What if the updated quantity is higher than the original and now an order (sub) total condition is met that wasn't met before? I personally always calculate a rate, even a zero one, but I guess there might be value-based configurations too. Maybe we should check, if the order item is a shippable purchasable entity, and react on every quantity change?

+        $shipping_rate = $shipment->getShippingMethodId() . '--' . $shipment->getShippingService();
+        $shipment_rates = $this->shipmentManager->calculateRates($shipment);

$shipment_rates is actually an array of \Drupal\commerce_shipping\ShippingRate objects, so shouldn't we call that $shipping_rates instead? And $shipping_rate is a string used to identify a rate, but the name suggests that it's rather a ShippingRate object too. So shouldn't we call it $shipping_rate_id or $shipping_rate_identifier instead?

valic’s picture

@agoradesign there is test coverage for these cases. (increasing quantity on a cart, and calculating new rates)

If you increase quantity, and your current rate needs to be recalculated, new is going to be assigned.

You need to see the entire block of code why decided to go with this string
$shipping_rate = $shipment->getShippingMethodId() . '--' . $shipment->getShippingService();

calculateRates returns rates keyed by the example above, as $shipping_rate. So for easier / faster logic with isset check.

Let say we have two shipping methods:
- 5 products - shipping rate is 10$
- more than 5 products - free shipping

So we have 2 products in cart - and shipping cost 10$.
If I add an increase to 4 products in the cart, shipping is the same

calculateRates fetch all available rates, and one among them is $shipping_rate -> so we don't change anything. Current rates are valid (even if we have more valid rates, but why changed it to something else, while we quessing here)

If I add an increase to 10 products in the cart, shipping should be updated.
We don't have any more inside calculateRates our shipping rate
if (!isset($shipment_rates[$shipping_rate]))

so we select a new rate and update it

        // On order item update we may need to use different shipping methods.
        $shipping_rate = $shipment->getShippingMethodId() . '--' . $shipment->getShippingService();
        $shipment_rates = $this->shippingManager->calculateRates($shipment);

        // There is no rates for shipping. Clear that shipment.
        if (empty($shipment_rates)) {
          unset($shipments[$key]);
          $shipment->delete();
          continue;
        }

        // Assign new shipping rate only if existing is not among
        // listed available rates for shipment.
        if (!isset($shipment_rates[$shipping_rate])) {
          $rate = $this->shippingManager->selectDefaultRate($shipment, $shipment_rates);
          $shipping_method_storage = $this->entityTypeManager->getStorage('commerce_shipping_method');
          /** @var \Drupal\commerce_shipping\Entity\ShippingMethodInterface $shipping_method */
          $shipping_method = $shipping_method_storage->load($rate->getShippingMethodId());
          $shipping_method_plugin = $shipping_method->getPlugin();
          $shipping_method_plugin->selectRate($shipment, $rate);
        }
agoradesign’s picture

@valic thanks for the explanation. I just wanted to be sure that this works.

Regarding the variable naming - I do have seen, why there is this string, and that's fine. But I recommend a different naming, like $shipping_rate_identifier instead of $shipping_rate, as well as $shipping_rates instead of $shipment_rates. This would improve readability a lot

valic’s picture

Assigned: Unassigned » valic

@agoradesign agree, naming then would be more self-explanatory.

Let me reroll the patch. Also, conditions with order subtotal or any similar are going to play well with this patch. (except order total price because of stil of #68 #69.

Planned to do test coverage based on amounts, but would require plugin which has shipping adjustments excluded

valic’s picture

valic’s picture

Assigned: valic » Unassigned

Updated naming. Plus corrected one old comment

-   * Constructs a new LateOrderProcessor object.
+   * Constructs a new EarlyOrderProcessor object.
jsacksick’s picture

Assigned: Unassigned » jsacksick

I've been reviewing this and planning on posting an updated patch later today (Probably minor changes)...

jsacksick’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
21.01 KB

I've been making some changes to the patch, but still not 100% satisfied with it...

First of all, I'm wondering if the rates recalculation code is necessary in populateFromProposedShipment() (we by the way don't call selectDefaultRate and manually use the first rate available()...).

Also, I'm suspecting the rate to be applied, even if no rates were already applied.
I think our test coverage is insufficient...

Also, wondering about the following:

  1. What happens when adding a new order item? Since we react to update/delete (Are we not recalculating there?)
  2. I'm pretty convinced we should be used cart events instead, this way we target cart operations more cleanly, and we have direct access to the order object (instead of getting it from the order item). If we do use cart events, we'll have to make sure Commerce API is using the cart manager to update order items.
  3. I moved the FORCE_REFRESH constant to the ShippingOrderManagerInterface
  4. Related to 2) Our functional test focuses on testing cart/checkout, so we don't even test order item CRUD.
  5. Is there any risk of updating the rates without the customer noticing?

I'll submit a different patch which will use the cart events instead so we can choose.

mglaman’s picture

So we need to:

  • Use CartEvents (discussed with @jsacksick and @valic and I believe we're on agreement here)
  • Expand test coverage to also add an item to the cart.

Yeah?

mglaman’s picture

Following populateFromProposedShipment, let's check when it is used. Maybe that `@todo` was moot.

jsacksick’s picture

FileSize
22.74 KB
5.91 KB

I expanded test coverage to cover adding a new item to the cart (This wasn't recalculating the rates, it is now).

I also managed to remove the duplicated logic in populateFromProposedShipment() and I'm pretty sure we don't need it anymore after the change I made in EarlyOrderProcessor.

I discovered a "bug" while running the tests, since we were deleting the shipments when no rates were returned we were loosing data (mainly the shipping profile), I fixed that by setting the amount, original_amount to NULL, as well as the shipping method (perhaps we should move that to a method on the ShipmentManager).

jsacksick’s picture

I'm fine with keeping the order item subscriber (instead of cart events, the result would be the same anyway).... And we implement the cart event for adding an order item to the cart...

mglaman’s picture

Status: Needs review » Needs work
+++ b/src/EarlyOrderProcessor.php
@@ -86,23 +86,20 @@
+        $shipment->set('shipping_method', NULL);
+        continue;

We also need to nullify shipping_service

jsacksick’s picture

Status: Needs work » Needs review
FileSize
22.79 KB
547 bytes

As discussed on Slack, we should open a follow-up to move this logic in a resetRate() method on something on the shipment itself.

Status: Needs review » Needs work

The last submitted patch, 88: 2957513-88.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jsacksick’s picture

Status: Needs work » Needs review
FileSize
22.86 KB
619 bytes

  • jsacksick committed 1ce5c00 on 8.x-2.x
    Issue #2957513 by valic, jsacksick, agoradesign, mglaman: Order item...
jsacksick’s picture

Status: Needs review » Fixed

Committed! Thanks everyone!

Status: Fixed » Closed (fixed)

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

FiNeX’s picture

Great work! I will try soon the update, thanks :-)

golubovicm’s picture

This is not working well for me:
I have shipping method in value of 5CHF and with condition "Current order total Less than 50CHF"
I'm testing with cart item in value of 42CHF so only 1 should trigger shipping costs and more than 2 shouldn't.
Situation I have actually is that calculation should shipping be applied or not is made based on previous cart state. So if I had 1 that item before cart update shipping is added. And if I had more than 1 before cart was updated shipping is not added. Shipping costs are always 1 cart update behind somehow.

Another issue I'm facing is that I do not see any shipping costs (conditional or not) until I go to offsite payment and return (by canceling the process of payment). After returning shipping is shown. Before that there is no shipping information of any kind. "Update cart" on cart page or "Recalculate shipping" order information page are not helping.

- So there is some action which is triggered when user is redirected to offsite payment only and should be executed earlier, most likely every time cart page is displayed.
- Cart update is done in some wrong order - first items number should be updated, total sum calculated and then shipment calculations called. From my point of view it's done opposite way so shipping is calculated before cart is updated?