Problem/Motivation
Whenever a shipment is added, edited, or removed the order is refreshed
https://git.drupalcode.org/project/commerce_shipping/-/blob/8.x-2.x/src/...
While this is fine for draft orders and carts it can lead to data loss when the order is refreshed for placed orders. Placed orders should not change unless expressly done by an administrator. The order refresh causes line item prices to be updated, promotions to be reevaluated, taxes recalculated.
When shipments are changed there are three things that do need to happen on placed orders:
- The shipping adjustment total on the order needs to be updated to reflect the total cost of the shipments
- The shipping promotions need to be recalculated. Other promotions need to remain untouched.
- The tax for shipping, if applicable, needs to be recalculated and an event fired for any tax integrations to subscribe to
Steps to reproduce
Add, edit, or delete a shipment using the Shipments tab UI in the order administration area after an order has already been placed.
Proposed resolution
Trigger a shipping specific order refresh on non draft orders.
Possible ways of doing this:
Call the provided functions that commerce_shipping defines for the order refresh to only refresh shipping specific parts of the order. Probably the wrong way.
Provide a shipment refresh service that handles the refresh similar to the core one but skips anything that is not related to shipping.
| Comment | File | Size | Author |
|---|---|---|---|
| #4 | 3328162-non-draft-orders-are-refreshed-4.patch | 1.82 KB | rhovland |
| #3 | 3328162-non-draft-orders-are-refreshed-3.patch | 1.85 KB | rhovland |
Issue fork commerce_shipping-3328162
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:
Comments
Comment #2
rhovlandComment #3
rhovlandHere's a patch that disables refresh unless it's a draft order and does the bare minimum: updates the shipping adjustments after shipment save.
As I argued in the related issue, non draft order edits should be in control of the administrative user and doing things that change the order in meaningful ways (taxes, promotions, etc) should be controlled by the admin user not an automated process.
If a store wants to honor the promotions they gave the customer at time of purchase or recalculate them should be up to the individual store.
Taxes are recalculated and records adjusted once the admin user is done making changes to the order. An automated refresh cannot predict when a good time to do that is.
Comment #4
rhovlandUpdated patch for latest version of module
Comment #5
anybodySetting needs review for maintainer feedback on the general points.
Comment #6
zaporylieI decided to bump the severity to Critical as items can also be deleted from the cart if one of the availability checkers returns that the item is not available - see AvailabilityOrderProcessor => AvailabilityManager
Comment #7
jsacksick commentedDefinitely bad that the order is refreshed regardless of the order state. I don't believe there is a need to actually check the order state. The order is always refreshed on save if it's a draft order, so setting the refresh state shouldn't even be necessary, but I guess this makes it explicit and make the intent clearer.
The new approach should work in most cases, however, not in all cases. The
LateOrderProcessorwhich is normally invoked on order refresh transfers shipment adjustments to the order.So with this change, the shipment tax isn't properly recalculated / reapplied for example. I'm thinking we might need to invoke this logic ourselves...
What we need to do is remove all prior shipping tax adjustments added to the order by the shipping tax type, and then somehow reapply them.
Any other custom logic applying shipment adjustments is not going to be invoked...But, this would put us in a much better situation than the one we're currently in...
Comment #8
jsacksick commentedWe're likely missing an equilavent of the
OrderRefreshfor shipments, aShipmentRefreshservice, but it's not that simple as the shipment affects the order.The problem with the approach described in my previous comment is that it lives in the form and doesn't address adjustments made to the shipments over the API for example (Programatic updates, or updates via JSONAPI endpoints for example).
Comment #9
jsacksick commentedNot sure what to do regarding taxes. We could check if there are tax types using the shipping tax type plugin then remove any prior shipping tax adjustments added to the order.
But that assumes the shipping tax is applied by the shpping local tax type plugin, if a service is used for tax calculation, it wouldn't be invoked.
And in other cases, a fee could be added to the shipping which potentially needs a recalculation as well?
So I don't really know whether I should add code to perform a tax recalculation for the case where there were prior tax adjustments added by the Shipping tax type plugin or not? It probably is better than not doing any recalculation?
Comment #10
zaporylieI think it's really convenient, from the OMS point of view, to be able to create shipments and have the order updated accordingly. Having to manually add adjustments would be very inconvenient. Having price adjustments automatically set, but tax adjustments not, could somehow be worse, as it would give a false belief that everything is happening automatically until you realize taxes are not there.
IMHO, this issue could potentially benefit from a change to the OrderRefresh flow that would allow running a subset of order pre-/processors on placed orders by introducing order refresh scopes. Scopes would be defined as tagged service attributes and if missing, in the current release cycle we can assume they are only meant to run for draft orders. However, we could provide scopes for placed or even completed orders and run a curated subset of processors that we know can be run deterministically, and the one copying shipping adjustment is a prime example of such a processor. Tax processor could potentially also be safe to run if we resolve taxes for a specific date (current vs placed).
Comment #11
jsacksick commentedI don't think it's as simple as that. Processors are kinda dependent indirectly on each other... It's expected that taxes are calculated post promotions for example.
Promotions cannot be reapplied on a placed order, but if quantities were adjusted, promotions should in a way, be recalculated as there would be a risk in bringing the total further down than expected. There are more examples, but it's a very tricky subject.
Comment #12
jsacksick commentedWe've decided internally to simply stop refreshing non draft orders for now, without trying to mess with the adjustments in the case the order isn't a draft, just to address this critical bug report until we address this properly.
With that said, it could be that the tests are broken by the change now, let's see.
Comment #14
jsacksick commentedCreated a change record: https://www.drupal.org/node/3545609
Comment #16
jsacksick commented