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

[WMS][12.0] Add stock_picking_type_routing_operation - alpha version #639

Closed
wants to merge 21 commits into from

Conversation

guewen
Copy link
Member

@guewen guewen commented Jul 5, 2019

Route explains the steps you want to produce whereas the “picking routing
operation” defines how operations are grouped according to their final source
and destination location.

This allows for example:

  • To parallelize picking operations in two locations of a warehouse, splitting
    them in two different picking type
  • To define pre-picking (wave) in some sub-locations, then roundtrip picking of
    the sub-location waves

Context for the use cases:

In the warehouse, you have a High-Bay which requires to place goods in a
handover when you move goods in or out of it. The High-Bay contains many
sub-locations.

A product can be stored either in the High-Bay, either in the Shelving zone.

When picking:

When there is enough stock in the Shelving, you expect the moves to have the
usual Pick(Highbay)-Pack-Ship steps. If the good is picked from the High-Bay, you will
need an extra operation: Pick(Highbay)-Handover-Pack-Ship.

This is what this feature is doing: on the High-Bay location, you define
a "routing operation". A routing operation is based on a picking type.
The extra operation will have the selected picking type, and the new move
will have the source destination of the picking type.

When putting away:

A put-away rule targets the High-Bay location.
An operation Input-Highbay is created. You expect Input-Handover-Highbay.

You can configure a routing operation for the put-away on the High-Bay Location.
The picking type of the new Handover move will the routing operation selected,
and its destination will be the destination of the picking type.

This is the implementation of "Warehouse operations by zones" RFC: #640 original document is the WMS document:
https://docs.google.com/document/d/1mct6bFFWJqW01wGFcjc-uQNEjyCxvh6Y9TuFdRhe-b0/edit#heading=h.49w4ly4e5y8g

Try on runbot

  • In Inventory Settings, activate:

    • Storage Locations
    • Multi-Warehouses
    • Multi-Step Routes

The initial setup in the demo data contains locations:

  • WH/Stock/Highbay
  • WH/Stock/Highbay/Bin 1
  • WH/Stock/Highbay/Bin 2
  • WH/Stock/Handover

The "Highbay" location (and children) is configured to:

  • create a source routing operation from Highbay to Handover when
    goods are taken from Highbay (using a new picking type Highbay → Handover)
  • create a destination routing operation from Handover to Highbay when
    goods are put to Highbay (using a new picking type Handover → Highbay)

Steps to try the Source Routing Operation:

  • In the main Warehouse, configure outgoing shipments to "Send goods in output and then deliver (2 steps)"
  • Inventory a product, for instance "[FURN_8999] Three-Seat Sofa", add 50 items in "WH/Stock/Highbay/Bay A/Bin 1", and nowhere else
  • Create a sales order with 5 "[FURN_8999] Three-Seat Sofa", confirm
  • You'll have 3 transfers; a new one has been created dynamically for Highbay -> Handover.

Steps to try the Destination Routing Operation:

  • In the "WH/Stock" location, create a Put-Away Strategy with:

    • "[DESK0004] Customizable Desk (Aluminium, Black)" to location "WH/Stock/Highbay/Bay A/Bin 1"
    • "[E-COM06] Corner Desk Right Sit" to location "WH/Stock/Shelf 1"
  • Create a new purchase order of:

    • 5 "[DESK0004] Customizable Desk (Aluminium, Black)"
    • 5 "[E-COM06] Corner Desk Right Sit"
  • Confirm the purchase

  • You'll have 2 transfers:

    • one to move DESK0004 from Supplier → Handover and E-COM06 from Supplier → Shelf 1
    • one waiting on the other to move DESK0004 from Handover → WH/Stock/Highbay/Bay A/Bin 1 (the final location of the put-away)

When a move is assigned and its source location (or one of its parent) corresponds to a
picking type flagged "is_zone", a new move will be inserted with this
picking type.
@guewen guewen force-pushed the 12.0-add-stock_picking_zone branch from 1d5fa2e to 4ab2334 Compare July 5, 2019 08:28
@rousseldenis
Copy link
Sponsor Contributor

@guewen Is this linked to the doc @jgrandguillaume wrote ?

If yes, it should be useful to write an issue with tasks. What do yout think ?

@jgrandguillaume
Copy link
Member

jgrandguillaume commented Jul 5, 2019

Hi @rousseldenis ,

Yes it is. I wanted to do it, but truth is the deadline for the first POC are tight and I didn't cope with the loads... I try to fix this before end of next week, updating both https://docs.google.com/document/d/1mct6bFFWJqW01wGFcjc-uQNEjyCxvh6Y9TuFdRhe-b0/edit#heading=h.49w4ly4e5y8g and creating an issue here.

Regards,

Joël

@jgrandguillaume
Copy link
Member

jgrandguillaume commented Jul 5, 2019

Hi @guewen ,

Looks very promising :) I found one problem for now.

  • Between a zone output and the other places (e.g. shelf 1) it is a round trip picking, meaning we want ONE picking here that will go pick goods in all child of stock location. If anything to pick in the highbay (define as a zone), we need this additional picking for the highbay BUT the additional move you create must land in the existing PICK.

So to try to be more concrete, do the following with same config [1]:

  • Inventory "[FURN_8999] Three-Seat Sofa", add 50 items in "WH/Stock/Highbay/Bay A/Bin 1", and nowhere else
  • Inventory "Pedal Bin", add 20 items in "WH/Stock/Shelf 1"
  • Create a sales order with 5 "[FURN_8999] Three-Seat Sofa" and 2 "Pedal Bin", confirm
  • You'll have 4 pickings
    Selection_454

The WH/PICK/00003 is available to pick the "Pedal Bin" and the WH/PICK/00004 is waiting to get the goods out from the highbay.

We expect:

  • Only the WH/PICK/00003 to gets both lines: the "Pedal bin" available and the "[FURN_8999] Three-Seat Sofa" waiting from the another operation.

It means, after creating the additional move, we should assign the move in a picking (using assign_picking method). In that case we should know we already have an existing picking to classify this move in.

Regardless the number of goods to ship and number of zone, a roundtrip picking of a certain level in the location tree always contain all operation of its childs in a same picking. This is the essence of the roudtrip picking it-self. If we have more than one PICK, it's wrong.

Hope this is clear. A part from that, I think it works very well. I try to make a few inline comments on the naming or definition of fields if I can.

Regards,

Joël

[1]

  • In Inventory Settings, activate:
    • Storage Locations
    • Multi-Warehouses
    • Multi-Step Routes
  • In the main Warehouse, configure outgoing shipments to "Send goods in output and then deliver (2 steps)"

@jgrandguillaume
Copy link
Member

@rousseldenis I've done the issue. Let me know if this works for you like this:

Hope this works for you. feel free to tell me if you want anything different. cc @pedrobaeza

@guewen
Copy link
Member Author

guewen commented Jul 5, 2019

@jgrandguillaume thanks for the feedback! The issue you mention is solved.

@rousseldenis rousseldenis added this to the 12.0 milestone Jul 6, 2019
@guewen
Copy link
Member Author

guewen commented Jul 8, 2019

@jgrandguillaume I added support for splitting moves when they are sourced from different locations amongst which we have different zones/non-zones.

When a move has several move lines because it is sourced from different
locations, and these locations come from different zones, or from a zone
and another location which is not a zone, we have to split the move in
as many moves as we have zones, and chain them properly.
This is needed because otherwise, the move for, for instance taking
goods from a shelf would have to wait on the move taking from the
Higbay which has been added in front of it. We expect that we can
already process the Shelf one.

The algorithm is to find all the zones of a move, split the move if any.
But to do so, we have to unreserve the move first. As we want to keep
the same quantities from the same locations (eg. 6 from the highbay bin
1), when we reserve again the quants, we have to force the reservation
system to take the goods from the same location than we had originally
(otherwise, the quantities that we used to split the move may change and
we are back to the beginning).
@guewen guewen force-pushed the 12.0-add-stock_picking_zone branch from 220768f to f8a5a7c Compare July 8, 2019 15:16
@guewen
Copy link
Member Author

guewen commented Jul 9, 2019

Build error is in stock_request_purchase and seems unrelated.

@jgrandguillaume
Copy link
Member

jgrandguillaume commented Jul 9, 2019

@guewen Works as expected. Thanks fort the work.
I've made a complex test case with:

  • Two main areas under stock (shelf 1 and 2)
  • One fridge in a separated zone with dedicated picking.type
  • One kardex with handover
  • One Highbay with handover

Setup with pick + pack + ship. Ordered quantities of product in each zones + in the highbay split of quantities in 2 separate bin.
All operations were computed and classified properly :)

Let's see the feeback after customer workshop.

@jgrandguillaume jgrandguillaume changed the title [DRAFT] Add stock_picking_zone [WIP][12] Add stock_picking_zone Jul 12, 2019
@jgrandguillaume jgrandguillaume changed the title [WIP][12] Add stock_picking_zone [WIP][12.0] Add stock_picking_zone Jul 12, 2019
@jgrandguillaume jgrandguillaume changed the title [WIP][12.0] Add stock_picking_zone [WIP][WMS][12.0] Add stock_picking_zone Jul 12, 2019
Copy link
Member

@jgrandguillaume jgrandguillaume left a comment

Choose a reason for hiding this comment

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

LGMT, functional test ok, Add the development status (aplha) and ready to merge

@guewen guewen changed the title [WIP][WMS][12.0] Add stock_picking_zone [WMS][12.0] Add stock_picking_zone - alpha version Aug 28, 2019
@guewen guewen marked this pull request as ready for review August 28, 2019 13:26
Copy link
Sponsor Contributor

@rousseldenis rousseldenis left a comment

Choose a reason for hiding this comment

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

First little review

# it is important to assign the zones first
for location_id, new_move_ids in new_move_per_location.items():
new_moves = self.browse(new_move_ids)
new_moves.with_context(
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

@guewen Using new context should be avoided as possible (especially in a loop).

Did you try to override the 'should_bypass_reservation()' function defined on location model ?
That one would bypass the calls to '_update_reserved_quantity' etc... on quant model. So, you can call your process to reserve.

For the exclude_apply_zone, would a test on move location_id is a zone on _action_assign will work ?

def _action_assign(self):
        super()._action_assign()
        moves_not_zone = self.filtered(lambda m: m.<is not a zone()>)
            moves = moves_not_zone._split_per_zone()
            moves._apply_move_location_zone()

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I'll have a look.

continue

move._do_unreserve()
move_to_assign_ids.add(move.id)
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

Why not using recordset directly? So, under you can avoid a browse()

Copy link
Member Author

Choose a reason for hiding this comment

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

I have had awful performance issues with adding ids to a recordset in a loop. Maybe it's not an issue anymore in 12.0 though...

if move.state not in ('assigned', 'partially_available'):
continue

pick_type_model = self.env['stock.picking.type']
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

Put it outside the loop

# At this point, we should not have lines with different zones,
# they have been split in _split_per_zone(), so we can take the
# first one
source = move.move_line_ids[0].location_id
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

Prefer using first:

from odoo.fields import first
first(move.move_line_ids).location_id

class StockPickingType(models.Model):
_inherit = 'stock.picking.type'

is_zone = fields.Boolean(
Copy link
Member Author

Choose a reason for hiding this comment

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

We'll probably want to rename this (as well as the module's name) by something more specific than zone.

Copy link
Contributor

@grindtildeath grindtildeath left a comment

Choose a reason for hiding this comment

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

IMO, usage of zone is a bit confusing as the vars sometimes refer to a picking.type and sometimes to a stock.location.

stock_picking_zone/demo/stock_location_demo.xml Outdated Show resolved Hide resolved
stock_picking_zone/demo/stock_location_demo.xml Outdated Show resolved Hide resolved
stock_picking_zone/models/stock_picking_type.py Outdated Show resolved Hide resolved
stock_picking_zone/models/stock_picking_type.py Outdated Show resolved Hide resolved
stock_picking_zone/models/stock_move.py Outdated Show resolved Hide resolved
@grindtildeath
Copy link
Contributor

@guewen Can you please merge guewen#2 ?

@guewen guewen changed the title [WMS][12.0] Add stock_picking_zone - alpha version [WMS][12.0] Add stock_picking_type_routing_operation - alpha version Sep 13, 2019
Move is_zone flag to stock.location picking_type M2o
Replace occurences of zone for routing operation
@jgrandguillaume jgrandguillaume mentioned this pull request Sep 13, 2019
32 tasks
guewen and others added 7 commits October 30, 2019 15:01
Rename the existing field and methods to differentiate source and
destination routing operations.
The reason is that we have to keep moves that come from the
same source in the same picking.

If we have 2 products, one with a routing, the other without, we
expect:

+-----------------------------------------------+
| IN/xxxx                                       |
| Product1 Supplier → Input                     |
| Product2 Supplier → Input                     |
+-----------------------------------------------+

+-----------------------------------------------+
| INT/xxxx                                      |
| Product1 Input → Stock/Handover               |
| Product2 Input → Shelf1                       |
+-----------------------------------------------+

the new one with our routing picking type:
+-----------------------------------------------+
| HO/xxxx                                       |
| Product1 Stock/Highbay/Handover → Highbay1-2  |
+-----------------------------------------------+

And not:

+-----------------------------------------------+
| IN/xxxx                                       |
| Product1 Supplier → Input                     |
| Product2 Supplier → Input                     |
+-----------------------------------------------+

+-----------------------------------------------+
| HO/xxxx                                       |
| Product1 Input → Stock/Handover               |
+-----------------------------------------------+

the new one with our routing picking type:
+-----------------------------------------------+
| INT/xxxx                                      |
| Product1 Stock/Highbay/Handover → Highbay1-2  |
| Product2 Input → Shelf1                       |
+-----------------------------------------------+
From @jbaudoux comment on review: "action_assign can create package level, so you need to destroy it when you unreserve"

Co-Authored-By: Jacques-Etienne Baudoux <je.baudoux@gmail.com>
Skip creation of a routing move when the destination is already
the destination of the routing picking type
@guewen guewen force-pushed the 12.0-add-stock_picking_zone branch from fc3ea1e to d7bb61e Compare December 6, 2019 14:34
@florian-dacosta
Copy link
Contributor

Hi @guewen
Thanks a lot for your continuous work on this !
I want to backport this module to version 8.
My use case is more about parallelizing the picking step and has nothing to do with locations needing an extra step like High-Bay in your example.
So, the splitting in wave is not systematic, because we may want to parallelize or not depending on different reasons. (here, it will depends on the carrier for instance.)

With the module as it is, it seems that applying the move_routing_operation is kind of systematic.
The only way to avoid it is passing the context exclude_apply_routing_operation and then it is by passed globally (for all moves that are being reserved).

In order to be a bit more flexible, I'd like to add a possibility to avoid this logic at move level.
I have made a PR here for this : guewen#5
Does it seem acceptable ?

I was also wondering about the problem that could bring a wrong stock level.
Imagine in Odoo we have 1 product in a location needing an extra routing operation, but we, physically have no product.
The picking will be splitted during reservation and the move rerouted. Then, after an inventory is done, we won't have any product in the location and the picking/move will be kind of stuck, no?

Let's take an example with the High-Bay/Handover case.
got 1 product A in Highbay location.

A picking PICK (WH/stock => WH/output) containing 1 move with 1 product A.

At reservation time, we will have :
a picking PICK-HIGHBAY (wh/stock => WH/Stock/Handover)
With reservation made in WH/stock/highbay.
Picking type is PICK-HIGHBAY (WH/Highbay => WH/Stock/Handover)

  • a new picking PICK (WH/stock/Handover => WH/output)

Then, when the inventory is done, there is not more product A in Highbay, but the pickings PICK-HIGHBAY and PICK are still there.
One issue I see is that the source location of the PICK-HIGHBAY move is still WH/Stock.
Meaning, it could then reserve another product anywhere in WH/stock, and would still need to bring the product to WH/Stock/Handover and then to WH/output.
When the split by operation is done, shouldn't we replace the move source location from WH/stock to WH/stock/Highbay instead of forcing the reservation to WH/stock/Highbay ?

This way, the move would still be stuck in Highbay, which is still not good but seems less a problem than reserving the stock in a location that has nothing to do with the Highbay or Handover location...
The best would be to revert the whole routing operation split I guess, but seems complicated...
What do you think? Is this a problem for your case?

move_lines = {}
for move_line in move.move_line_ids:
location = move_line.location_id
move_lines[location] = sum(move_line.mapped("product_uom_qty"))
Copy link
Contributor

Choose a reason for hiding this comment

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

@guewen
Here we consider product_uom_qty, but later in the split, Odoo expects the quantity in default product uom.
I think we should consider the product_qty instead.

I made a quick test, and It seems it ends on working, but not the way we expect.
I have created a product A with 6 qty in WH/Stock/highbay and 18 in WH/Stock/shelf 1
I left default uom as unit.
Then, I have created a delivery order with qty 2 / product_uom dozen

After reservation, I have
The Highbay=>Handover with 2 moves with unit as uom (with qty 0.5 and 5.5)
Then I also have a delivery order with one move, with dozen as uom and qty 1.5.
Well, at last, it is all kind of good, but it is weird that the uom of stock move changes during reservation, and it is weird that we have 2 moves with qty 0.5 and 5.5 in the same picking.

I guess if we work with qty in product uom, it will resolve all of this.
I did not test, but I guess something similar happens in _split_per_dest_routing_operation

@guewen
Copy link
Member Author

guewen commented Dec 13, 2019

@florian-dacosta thanks for your comment!

I want to backport this module to version 8.

Hmm beware, I'll do several changes in the data model and I'm not happy with the overall complexity. It's a moving target.
BTW, I opened #788 for 13.0 and will continue the work from there, closing this one (we won't need it in 12.0).

My use case is more about parallelizing the picking step and has nothing to do with locations needing an extra step like High-Bay in your example.
So, the splitting in wave is not systematic, because we may want to parallelize or not depending on different reasons. (here, it will depends on the carrier for instance.)

The particularity of this addon is that it modifies the routing depending on the move lines, so only after the reservation is done, and we know from where we have to take goods. The downside of this is the relative inefficiency, we have to reserve to know where to pick, unreserve, maybe split if we have different sources, and reserve again. If your use case does not depend on the move lines, it seems sub-optimal.

In order to be a bit more flexible, I'd like to add a possibility to avoid this logic at move level.
I have made a PR here for this : guewen#5

I merged it in my branch #788 but again, things will move.

The picking will be splitted during reservation and the move rerouted. Then, after an inventory is done, we won't have any product in the location and the picking/move will be kind of stuck, no?

Same thing as if you are in a 2/3 steps delivery I guess? You can probably move products in the location with an internal move or cancel the picking. @jgrandguillaume may have more insights.

When the split by operation is done, shouldn't we replace the move source location from WH/stock to WH/stock/Highbay instead of forcing the reservation to WH/stock/Highbay ?

Maybe.

@guewen guewen closed this Dec 13, 2019
@florian-dacosta
Copy link
Contributor

Hmm beware, I'll do several changes in the data model and I'm not happy with the overall complexity. It's a moving target.

Not a big issue, actually, I probably won't share it to the OCA, as it seems a lot more complexe to do in v8 with operations instead of stock move lines.
So it will be quite specific, I'll follow the evolution. My goal beeing to be able to use the OCA module when migrating.

I merged it in my branch #788 but again, things will move.
Again, not a problem if thins changes, the idea is to have the right hooks so the module may be flexible.

If the module changes but we keep the possibility to by pass the logic for a specific move, that's perfect. Anyway, I'll keep following.
Did you also had a chance to check guewen#6 ?
This would be great to have this possibility too.

@florian-dacosta
Copy link
Contributor

florian-dacosta commented Dec 13, 2019

@guewen

If your use case does not depend on the move lines, it seems sub-optimal.

Well my case also depend on the stock move line.
Not sure yet if doing it at reservation time is the best in my case (we may want to do it right before starting the preparation) but the whole logic of splitting and re-routing is the same (just some exceptions for some move, that's why the hooks are welcome!) so it would be a shame not to use this module I guess.

i-vyshnevska pushed a commit to camptocamp/stock-logistics-warehouse that referenced this pull request Apr 13, 2020
Signed-off-by pedrobaeza
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.

None yet

6 participants