-
-
Notifications
You must be signed in to change notification settings - Fork 719
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add compatibility with stock_dynamic_routing
in stock_move_common_dest_sync_location Some notes: * syncing the destinations could not be done during _action_assign: if we call _action_done on several moves at once with different locations, we don't expect them to change of destination under the hood! So we sync after _action_done * For now, no code is needed in stock_dynamic_routing_common_dest_sync to make the modules compatibles, but it contains tests with routing + sync
- Loading branch information
Showing
12 changed files
with
387 additions
and
58 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
...stock_dynamic_routing_common_dest_sync/odoo/addons/stock_dynamic_routing_common_dest_sync
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../stock_dynamic_routing_common_dest_sync |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import setuptools | ||
|
||
setuptools.setup( | ||
setup_requires=['setuptools-odoo'], | ||
odoo_addon=True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Copyright 2019 Camptocamp (https://www.camptocamp.com) | ||
{ | ||
"name": "Stock Routing Operations - Dest. Sync", | ||
"summary": "Glue module", | ||
"author": "Camptocamp, Odoo Community Association (OCA)", | ||
"website": "https://github.com/OCA/stock-logistics-warehouse", | ||
"category": "Warehouse Management", | ||
"version": "13.0.1.0.0", | ||
"license": "AGPL-3", | ||
"depends": [ | ||
# FIXME will be renamed to stock_dynamic_routing | ||
"stock_routing_operation", | ||
"stock_move_common_dest_sync_location", | ||
], | ||
"demo": [], | ||
"data": [], | ||
"auto_install": True, | ||
"installable": True, | ||
"development_status": "Alpha", | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* Guewen Baconnier <[email protected]> |
5 changes: 5 additions & 0 deletions
5
stock_dynamic_routing_common_dest_sync/readme/DESCRIPTION.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Glue module between ``stock_move_common_dest_sync_location`` and | ||
``stock_dynamic_routing``. | ||
|
||
Currently, the module only contains tests to verify the compatibility | ||
between these two modules, but compatibility code may be needed later. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import test_routing_sync |
208 changes: 208 additions & 0 deletions
208
stock_dynamic_routing_common_dest_sync/tests/test_routing_sync.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
# Copyright 2020 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
|
||
from odoo.addons.stock_move_common_dest_sync_location.tests.test_move_common_dest_sync_location import ( # noqa | ||
TestCommonSyncDest, | ||
) | ||
|
||
|
||
class TestRoutingPullWithSync(TestCommonSyncDest): | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.location_pack_load = cls.env["stock.location"].create( | ||
{"name": "Packing Load", "location_id": cls.packing_location.id} | ||
) | ||
cls.location_pack_post = cls.env["stock.location"].create( | ||
{"name": "Packing Post", "location_id": cls.packing_location.id} | ||
) | ||
cls.location_pack_post_bay1 = cls.env["stock.location"].create( | ||
{"name": "Bay 1", "location_id": cls.location_pack_post.id} | ||
) | ||
cls.pack_post_type = cls.pack_type.copy( | ||
{ | ||
"name": "Packing Post", | ||
"sequence_code": "WH/POST", | ||
"default_location_src_id": cls.location_pack_post.id, | ||
} | ||
) | ||
cls.routing = cls.env["stock.routing"].create( | ||
{ | ||
"location_id": cls.location_pack_post.id, | ||
"rule_ids": [ | ||
(0, 0, {"method": "pull", "picking_type_id": cls.pack_post_type.id}) | ||
], | ||
} | ||
) | ||
cls._update_qty_in_location(cls.stock_shelf_location, cls.product_1, 10) | ||
cls._update_qty_in_location(cls.stock_shelf_location, cls.product_2, 10) | ||
|
||
cls.pick_move1 = cls._create_single_move(cls.pick_type, cls.product_1) | ||
cls.pack_move1 = cls._create_single_move( | ||
cls.pack_type, cls.product_1, move_orig=cls.pick_move1 | ||
) | ||
cls.pick_move2 = cls._create_single_move(cls.pick_type, cls.product_2) | ||
cls.pack_move2 = cls._create_single_move( | ||
cls.pack_type, cls.product_2, move_orig=cls.pick_move2 | ||
) | ||
cls.pick_move3 = cls._create_single_move(cls.pick_type, cls.product_2) | ||
cls.pack_move3 = cls._create_single_move( | ||
cls.pack_type, cls.product_2, move_orig=cls.pick_move3 | ||
) | ||
moves = ( | ||
cls.pick_move1 | ||
+ cls.pack_move1 | ||
+ cls.pick_move2 | ||
+ cls.pack_move2 | ||
+ cls.pick_move3 | ||
+ cls.pack_move3 | ||
) | ||
moves._assign_picking() | ||
moves._action_assign() | ||
|
||
def assert_picking_type_pack(self, record): | ||
self.assertEqual(record.picking_type_id, self.pack_type) | ||
|
||
def assert_picking_type_pack_post(self, record): | ||
self.assertEqual(record.picking_type_id, self.pack_post_type) | ||
|
||
def assert_src_packing(self, record): | ||
self.assertEqual(record.location_id, self.packing_location) | ||
|
||
def assert_dest_packing(self, record): | ||
self.assertEqual(record.location_dest_id, self.packing_location) | ||
|
||
def assert_src_pack_post(self, record): | ||
self.assertEqual(record.location_id, self.location_pack_post) | ||
|
||
def assert_src_pack_post_bay1(self, record): | ||
self.assertEqual(record.location_id, self.location_pack_post_bay1) | ||
|
||
def assert_src_pack_load(self, record): | ||
self.assertEqual(record.location_id, self.location_pack_load) | ||
|
||
def assert_dest_pack_post(self, record): | ||
self.assertEqual(record.location_dest_id, self.location_pack_post) | ||
|
||
def assert_dest_pack_post_bay1(self, record): | ||
self.assertEqual(record.location_dest_id, self.location_pack_post_bay1) | ||
|
||
def assert_dest_pack_load(self, record): | ||
self.assertEqual(record.location_dest_id, self.location_pack_load) | ||
|
||
def test_pack_sync(self): | ||
self.pack_type.sync_common_move_dest_location = True | ||
self.pack_post_type.sync_common_move_dest_location = True | ||
|
||
self.pick_move1.move_line_ids.write( | ||
{ | ||
"location_dest_id": self.location_pack_post_bay1.id, | ||
"qty_done": self.pick_move1.move_line_ids.product_uom_qty, | ||
} | ||
) | ||
self.pick_move1._action_done() | ||
|
||
self.assert_dest_pack_post(self.pick_move1) | ||
self.assert_dest_pack_post_bay1(self.pick_move1.move_line_ids) | ||
self.assert_dest_pack_post(self.pick_move1) | ||
self.assert_dest_pack_post_bay1(self.pick_move2.move_line_ids) | ||
self.assert_dest_pack_post(self.pick_move3) | ||
self.assert_dest_pack_post_bay1(self.pick_move3.move_line_ids) | ||
|
||
self.assert_src_pack_post(self.pack_move1) | ||
self.assert_src_pack_post_bay1(self.pack_move1.move_line_ids) | ||
# no move lines on these waiting moves: | ||
self.assert_src_pack_post(self.pack_move2) | ||
self.assert_src_pack_post(self.pack_move3) | ||
|
||
self.assert_picking_type_pack_post(self.pack_move1.picking_id) | ||
self.assert_picking_type_pack_post(self.pack_move2.picking_id) | ||
self.assert_picking_type_pack_post(self.pack_move3.picking_id) | ||
|
||
def test_pack_sync_all_at_once(self): | ||
self.pack_type.sync_common_move_dest_location = True | ||
self.pack_post_type.sync_common_move_dest_location = True | ||
|
||
self.pick_move1.move_line_ids.write( | ||
{ | ||
"location_dest_id": self.location_pack_post_bay1.id, | ||
"qty_done": self.pick_move1.move_line_ids.product_uom_qty, | ||
} | ||
) | ||
self.pick_move2.move_line_ids.write( | ||
{ | ||
"location_dest_id": self.location_pack_post_bay1.id, | ||
"qty_done": self.pick_move2.move_line_ids.product_uom_qty, | ||
} | ||
) | ||
self.pick_move3.move_line_ids.write( | ||
{ | ||
"location_dest_id": self.location_pack_load.id, | ||
"qty_done": self.pick_move3.move_line_ids.product_uom_qty, | ||
} | ||
) | ||
# done on all the picking at once: we expect the original destinations | ||
# to be kept | ||
self.pick_move1.picking_id.action_done() | ||
|
||
self.assert_dest_pack_post(self.pick_move2) | ||
self.assert_dest_pack_post_bay1(self.pick_move2.move_line_ids) | ||
self.assert_dest_packing(self.pick_move3) | ||
self.assert_dest_pack_load(self.pick_move3.move_line_ids) | ||
|
||
self.assert_src_pack_post(self.pack_move1) | ||
self.assert_src_pack_post_bay1(self.pack_move1.move_line_ids) | ||
self.assert_src_pack_post(self.pack_move2) | ||
self.assert_src_pack_post_bay1(self.pack_move1.move_line_ids) | ||
self.assert_src_packing(self.pack_move3) | ||
self.assert_src_pack_load(self.pack_move3.move_line_ids) | ||
|
||
self.assert_picking_type_pack_post(self.pack_move1.picking_id) | ||
self.assert_picking_type_pack_post(self.pack_move2.picking_id) | ||
# remains on the Pack picking type because it was moved to a | ||
# different location | ||
self.assert_picking_type_pack(self.pack_move3.picking_id) | ||
|
||
def test_pack_sync_split(self): | ||
self.pack_type.sync_common_move_dest_location = True | ||
self.pack_post_type.sync_common_move_dest_location = True | ||
|
||
self.pick_move1._do_unreserve() | ||
self.env["stock.quant"].search( | ||
[ | ||
("location_id", "=", self.stock_shelf_location.id), | ||
("product_id", "=", self.product_1.id), | ||
] | ||
).unlink() | ||
|
||
self._update_qty_in_location(self.stock_shelf_location, self.product_1, 1) | ||
|
||
self.pick_move1._action_assign() | ||
self.pick_move1.move_line_ids.write( | ||
{"location_dest_id": self.location_pack_post_bay1.id, "qty_done": 1} | ||
) | ||
self.pick_move1._action_done() | ||
pick_move_split = self.pick_move1.move_dest_ids.move_orig_ids - self.pick_move1 | ||
pack_move_split = pick_move_split.move_dest_ids - self.pack_move1 | ||
self.assertEqual(pick_move_split.state, "confirmed") | ||
self.assertEqual(self.pack_move1.state, "waiting") | ||
self.assertEqual(pack_move_split.state, "assigned") | ||
|
||
self.assert_dest_pack_post(self.pick_move1) | ||
self.assert_dest_pack_post_bay1(self.pick_move1.move_line_ids) | ||
self.assert_dest_pack_post(pick_move_split) | ||
self.assert_dest_pack_post(self.pick_move1) | ||
self.assert_dest_pack_post_bay1(self.pick_move2.move_line_ids) | ||
self.assert_dest_pack_post(self.pick_move3) | ||
self.assert_dest_pack_post_bay1(self.pick_move3.move_line_ids) | ||
|
||
self.assert_src_pack_post(pack_move_split) | ||
self.assert_src_pack_post_bay1(pack_move_split.move_line_ids) | ||
# no move lines on these waiting moves: | ||
self.assert_src_pack_post(self.pack_move1) | ||
self.assert_src_pack_post(self.pack_move2) | ||
self.assert_src_pack_post(self.pack_move3) | ||
|
||
self.assert_picking_type_pack_post(self.pack_move1.picking_id) | ||
self.assert_picking_type_pack_post(self.pack_move2.picking_id) | ||
self.assert_picking_type_pack_post(self.pack_move3.picking_id) |
90 changes: 64 additions & 26 deletions
90
stock_move_common_dest_sync_location/models/stock_move.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,79 @@ | ||
# Copyright 2020 Camptocamp SA | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) | ||
from odoo import models | ||
from odoo import fields, models | ||
|
||
|
||
class StockMove(models.Model): | ||
_inherit = "stock.move" | ||
|
||
def _action_assign(self): | ||
unassigned = self.filtered( | ||
lambda m: m.state not in ("assigned", "partially_available", "done") | ||
) | ||
super()._action_assign() | ||
unassigned.filtered( | ||
lambda m: m.state in ("assigned", "partially_available") | ||
)._sync_same_destination_orig_moves() | ||
def _action_done(self, cancel_backorder=False): | ||
to_sync = {} | ||
for move in self: | ||
# store the original moves that goes in the same transfer, because | ||
# when we call super(), changes made to the chain of moves (for | ||
# instance the application of a "dynamic routing" by | ||
# stock_dynamic_routing) may happen. | ||
if move.move_dest_ids.filtered(self._filter_sync_destination): | ||
to_sync[move] = move.common_dest_move_ids | ||
|
||
# When using dynamic routing, it will be applied applied during the | ||
# call to super. The routing can move a stock.move to another | ||
# stock.picking, insert a new stock.move... | ||
# We have to apply the synchronization of the destinations once the | ||
# move is done to be sure it was really the selected destination, and a | ||
# user started to move goods at this place. | ||
moves_todo = super()._action_done(cancel_backorder=cancel_backorder) | ||
|
||
for move, neighbours in to_sync.items(): | ||
if move.state != "done": | ||
continue | ||
|
||
# if any new move were added (split, extra move, ...) we have to | ||
# synchronize their destination location as well | ||
neighbours |= move.common_dest_move_ids | ||
|
||
# find the location where the neighbour moves (eg. the moves which | ||
# have to be packed together, so moved at the same place) amongst | ||
# the destination moves. We consider only assigned moves, which | ||
# means part of the goods have been moved there. | ||
dest_move = move.move_dest_ids.filtered( | ||
lambda m: m.state in ("assigned", "partially_available") | ||
and self._filter_sync_destination(m) | ||
) | ||
dest_move = fields.first(dest_move) | ||
dest_move_line = fields.first(dest_move.move_line_ids) | ||
|
||
# Sync the destinations to group the moves in the same | ||
# location. If a routing was applied to the assigned move, | ||
# the other waiting moves will now match the same routing | ||
# which will be applied. | ||
move._sync_destination_to_neighbour_moves( | ||
neighbours, dest_move.location_id, dest_move_line.location_id | ||
) | ||
return moves_todo | ||
|
||
@staticmethod | ||
def _filter_sync_destination(move): | ||
return move.picking_id.picking_type_id.sync_common_move_dest_location | ||
|
||
def _sync_same_destination_orig_moves(self): | ||
moves = self.filtered(self._filter_sync_destination) | ||
for move in moves.mapped("move_orig_ids"): | ||
neighbour_moves = move.common_dest_move_ids | ||
move._sync_destination_to_neighbour_moves(neighbour_moves) | ||
|
||
def _sync_destination_to_neighbour_moves(self, neighbour_moves): | ||
def _sync_destination_to_neighbour_moves( | ||
self, neighbour_moves, move_destination, move_line_destination | ||
): | ||
self.ensure_one() | ||
# get the destination of the first move which was done, we want all the other | ||
# moves, available or not, so all the goods will be moved to the same place | ||
destination = self.move_line_ids.mapped("location_dest_id") | ||
# moves destination locations are restricted to the same destination, | ||
# so user can't bring goods elsewhere than the good already moved | ||
neighbour_moves.filtered( | ||
lambda m: m.location_dest_id != destination and m.state != "done" | ||
).write({"location_dest_id": destination.id}) | ||
neighbour_moves = neighbour_moves.filtered(lambda m: m.state != "done") | ||
# Normally the move destination does not change. But when using other | ||
# addons, such as stock_dynamic_routing, the source location of the | ||
# destination move can change, so handle this case too. (there is a | ||
# glue module stock_dynamic_routing_common_dest_sync). | ||
moves_to_update = (self | neighbour_moves).filtered( | ||
lambda m: m.location_dest_id != move_destination | ||
) | ||
moves_to_update.write({"location_dest_id": move_destination.id}) | ||
# Sync the source of the destination move too, if it's still waiting. | ||
moves_to_update.move_dest_ids.filtered(lambda m: m.state == "waiting").write( | ||
{"location_id": move_destination.id} | ||
) | ||
lines = neighbour_moves.mapped("move_line_ids") | ||
lines.filtered( | ||
lambda l: l.location_dest_id != destination and l.state != "done" | ||
).write({"location_dest_id": destination.id}) | ||
lambda l: l.location_dest_id != move_line_destination and l.state != "done" | ||
).write({"location_dest_id": move_line_destination.id}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.