Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update order cancellation, sets inventory units to canceled #3781

Closed
wants to merge 1 commit into from
Closed

Update order cancellation, sets inventory units to canceled #3781

wants to merge 1 commit into from

Conversation

stefano-sarioli
Copy link
Contributor

@stefano-sarioli stefano-sarioli commented Sep 28, 2020

This PR fix #3719.

Before this commit when an order was canceled the inventory units
of the order will remain untouched. So there were canceled orders with
inventory units with the state on_hand or backordered.

This situation was not only inconsistent but can lead to subtle bugs
with the stock management system.
How to reproduce the bug:

  • Create an order for a single variant, but select a number of items greater than the available amount.
  • The order generates 2 shipments, one with the inventory units on_hand and one with the inventory units back-ordered.
  • Cancel the order.
  • The inventory units on_hand remains on_hand.
  • The inventory units back-ordered go to the on_hand state.
  • If you resume the order all the inventory units will be on_hand, and the shipment will be ready to be shipped.

This problem is caused by the fact that when the order is canceled, a movement is created to restock the stock_item correlated with the inventory_units. As soon as the stock_items get back to a positive value it will try to convert as much as possible back-ordered inventory units to on_hand.

order_complete_before

order_canceled_before

order_resumed_before

With this commit the inventory units related to an order are canceled when the order is canceled.

When an order is resumed we recalculate the state of the connected
inventory units, based on the actual stocks available.

order_complete_after

order_canceled_after

order_resumed_after

What do you think about this issue, and my solution?
I'm not sure that the testing is enough, probably I can add an E2E test, what do you think? any idea on what can be the best place to put this test?

Checklist:

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have updated Guides and README accordingly to this change (if needed)
  • I have added tests to cover this change (if needed)
  • I have attached screenshots to this PR for visual changes (if needed)

Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a preliminary concern, also some specs seem to be failing.

units_available, units_to_back_order = stock_location.fill_status(item.variant, item.quantity)

inventory_units.canceled.limit(units_available).each(&:on_hand!)
inventory_units.canceled.limit(units_to_back_order).each(&:backorder!)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is canceling an order the only way to cancel an IU at the moment? Just wondering if other IUs can have the state canceled and they would be resumed, even if not part of the order cancelation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you are right, there are other places.
I have to pick only the canceled units for the variant reported on the manifest items.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used the line_item to be sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I'm double-checking this to be sure. Sorry for the message spam 🙇 .

Before this commit when an order was canceled the inventory units
of the order will remain untouched. So there were canceled orders with
inventory units with the state on_hand or backordered.

This situation was not only inconsistent but can lead to subtle bugs
with the stock management system.

With this commit the inventory units related to an order are
canceled when the order is canceled.

When an order is resume we recalculate the state of the connected
inventory units, based on the actual stocks available.
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just left a question, overall I can't see any issues with this approach.

number_of_items_to_restore = item.states["on_hand"].to_i + item.states["backordered"].to_i

if item.variant.should_track_inventory?
units_available, units_to_back_order = stock_location.fill_status(item.variant, number_of_items_to_restore)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these variables filled with the current availability in the stock location, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, this will update the inventory units state based on the current stock level.

This is why inside #after_resume I have to call #resume_inventory_units before unstocking the items. If you call #resume_inventory_units after the unstocking the states of the inventory units will be wrong.

There is a test covering this use-case to avoid regression.

shipment.resume!
expect(shipment.state).to eq 'pending'
end
end

context "when any inventory was already canceled" do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this spec!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to you for pointing out the edge case 🙇, I totally missed it!

Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @stefano-sarioli !

@@ -34,6 +34,14 @@ module InventoryUnit
event :cancel do
transition to: :canceled, from: ::Spree::InventoryUnit::CANCELABLE_STATES.map(&:to_sym)
end

event :on_hand do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure about this event name.

Semantically speaking, event names should be verbs, as they actively change the state of an object. In this case, the action name is the same as the target state, and this may lead to some confusion too.
Also, there's the query method #can_on_hand? added automatically, which again does not make 100% sense to me.

What about changing the name to something like set_on_hand or make_on_hand? And if you find a better naming, just go for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you, I was not convinced too by the name of that event but couldn't think of something better.

I like set_on_hand proposed by you ❤️.

Sadly, as pointed out in the last comment of Kennyadsl, I'm probably going to close this PR to move the whole resume functionality out of Solidus.

Thanks a lot for your feedback, I'll note it down to use it when the work on the extracted functionality will start 🙏.

inventory_units.where.not(state: 'canceled').each(&:cancel!)
end

def resume_inventory_units
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this within the core team. We think that resuming concerns should be moved into a separate PR. There are a lot of opinions on how resuming an order should work and probably none of them will work as the majority of stores expect (everyone wants to restock items in different ways, depending on if they are using Solidus to manage stocks, which itself has a lot of variables, or they are relying on an external system).

We are thinking to actually deprecate the Resume order functionality. It's not used a lot and gives us more headaches than the benefits it provides to stores (none of us can think of a single store that ever used this functionality). The way to go here is moving the current behavior into a legacy extension, and deprecate the functionality in core (removing the Resume button from the admin UI). WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you, especially if it's an underused functionality.
My solution is far too general and makes a lot of assumptions about the stock management system.

I think that extracting the resume functionality into an external gem, maybe with this fix on it, would be the best solution.

Probably we can close this PR, I'll wait for the final decision about the resume functionality and will come back to contribute to that one as soon as it's ready.

Thank a lot for your feedback and consideration 🙏.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancellation part of this PR is well done and something that we definitely need in core. Please don't close the PR but just remove the part that resumes the inventory units. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Change inventory unit state to "canceled" when order canceled
4 participants