diff --git a/stock_picking_zone/README.rst b/stock_picking_type_routing_operation/README.rst
similarity index 100%
rename from stock_picking_zone/README.rst
rename to stock_picking_type_routing_operation/README.rst
diff --git a/stock_picking_zone/__init__.py b/stock_picking_type_routing_operation/__init__.py
similarity index 100%
rename from stock_picking_zone/__init__.py
rename to stock_picking_type_routing_operation/__init__.py
diff --git a/stock_picking_zone/__manifest__.py b/stock_picking_type_routing_operation/__manifest__.py
similarity index 92%
rename from stock_picking_zone/__manifest__.py
rename to stock_picking_type_routing_operation/__manifest__.py
index e86d9d487c88..8b4123dac55e 100644
--- a/stock_picking_zone/__manifest__.py
+++ b/stock_picking_type_routing_operation/__manifest__.py
@@ -15,7 +15,7 @@
'demo/stock_picking_type_demo.xml',
],
'data': [
- 'views/stock_picking_type_views.xml',
+ 'views/stock_location.xml',
],
'installable': True,
'development_status': 'Alpha',
diff --git a/stock_picking_zone/demo/stock_location_demo.xml b/stock_picking_type_routing_operation/demo/stock_location_demo.xml
similarity index 77%
rename from stock_picking_zone/demo/stock_location_demo.xml
rename to stock_picking_type_routing_operation/demo/stock_location_demo.xml
index d721e0a8f957..d29639c5d3f2 100644
--- a/stock_picking_zone/demo/stock_location_demo.xml
+++ b/stock_picking_type_routing_operation/demo/stock_location_demo.xml
@@ -3,8 +3,7 @@
Highbay
-
+
Bay A
@@ -21,8 +20,7 @@
Handover
-
+
diff --git a/stock_picking_zone/demo/stock_picking_type_demo.xml b/stock_picking_type_routing_operation/demo/stock_picking_type_demo.xml
similarity index 70%
rename from stock_picking_zone/demo/stock_picking_type_demo.xml
rename to stock_picking_type_routing_operation/demo/stock_picking_type_demo.xml
index 5761b5c40c19..1c583506eb6a 100644
--- a/stock_picking_zone/demo/stock_picking_type_demo.xml
+++ b/stock_picking_type_routing_operation/demo/stock_picking_type_demo.xml
@@ -15,10 +15,10 @@
internal
-
-
+
+
-
+
diff --git a/stock_picking_zone/models/__init__.py b/stock_picking_type_routing_operation/models/__init__.py
similarity index 74%
rename from stock_picking_zone/models/__init__.py
rename to stock_picking_type_routing_operation/models/__init__.py
index b2c5936e1170..200ba08be0a0 100644
--- a/stock_picking_zone/models/__init__.py
+++ b/stock_picking_type_routing_operation/models/__init__.py
@@ -1,3 +1,4 @@
+from . import stock_location
from . import stock_move
from . import stock_picking_type
from . import stock_quant
diff --git a/stock_picking_type_routing_operation/models/stock_location.py b/stock_picking_type_routing_operation/models/stock_location.py
new file mode 100644
index 000000000000..33c8032217e8
--- /dev/null
+++ b/stock_picking_type_routing_operation/models/stock_location.py
@@ -0,0 +1,55 @@
+# Copyright 2019 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+from odoo import api, models, fields, _
+from odoo.exceptions import ValidationError
+
+
+class StockLocation(models.Model):
+
+ _inherit = 'stock.location'
+
+ routing_operation_picking_type_id = fields.Many2one(
+ 'stock.picking.type',
+ string='Routing operation',
+ help="Change destination of the move line according to the"
+ " default destination setup after reservation occurs",
+ )
+
+ @api.constrains('routing_operation_picking_type_id')
+ def _check_routing_operation_picking_type_id(self):
+ for location in self:
+ picking_type = location.routing_operation_picking_type_id
+ if picking_type.default_location_src_id != location:
+ raise ValidationError(_(
+ 'A picking type for routing operations cannot have a'
+ ' different default source location than the location it '
+ 'is used on.'
+ ))
+
+ @api.multi
+ def _find_picking_type_for_routing_operation(self):
+ self.ensure_one()
+ # First select all the parent locations and the matching
+ # picking types. In a second step, the picking type matching the closest location
+ # is searched in memory. This is to avoid doing an SQL query
+ # for each location in the tree.
+ tree = self.search(
+ [('id', 'parent_of', self.id)],
+ # the recordset will be ordered bottom location to top location
+ order='parent_path desc'
+ )
+ picking_types = self.env['stock.picking.type'].search([
+ ('routing_operation_location_ids', '!=', False),
+ ('default_location_src_id', 'in', tree.ids)
+ ])
+ # the first location is the current move line's source location,
+ # then we climb up the tree of locations
+ for location in tree:
+ match = picking_types.filtered(
+ lambda p: p.default_location_src_id == location
+ )
+ if match:
+ # we can only have one match as we have a unique
+ # constraint on is_zone + source location
+ return match
+ return self.env['stock.picking.type']
diff --git a/stock_picking_zone/models/stock_move.py b/stock_picking_type_routing_operation/models/stock_move.py
similarity index 66%
rename from stock_picking_zone/models/stock_move.py
rename to stock_picking_type_routing_operation/models/stock_move.py
index a2afed1eaf67..efd6dedf3d86 100644
--- a/stock_picking_zone/models/stock_move.py
+++ b/stock_picking_type_routing_operation/models/stock_move.py
@@ -1,5 +1,5 @@
# Copyright 2019 Camptocamp (https://www.camptocamp.com)
-
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from itertools import chain
from odoo import models
@@ -9,19 +9,17 @@ class StockMove(models.Model):
def _action_assign(self):
super()._action_assign()
- if not self.env.context.get('exclude_apply_zone'):
- moves = self._split_per_zone()
- moves._apply_move_location_zone()
+ if not self.env.context.get('exclude_apply_routing_operation'):
+ moves = self._split_per_routing_operation()
+ moves._apply_move_location_routing_operation()
- def _split_per_zone(self):
+ def _split_per_routing_operation(self):
move_to_assign_ids = set()
new_move_per_location = {}
for move in self:
if move.state not in ('assigned', 'partially_available'):
continue
- pick_type_model = self.env['stock.picking.type']
-
# Group move lines per source location, some may need an additional
# operations while others not. Store the number of products to
# take from each location, so we'll be able to split the move
@@ -31,39 +29,42 @@ def _split_per_zone(self):
location = move_line.location_id
move_lines[location] = sum(move_line.mapped('product_uom_qty'))
- # We'll split the move to have one move per different zones where
+ # We'll split the move to have one move per different location where
# we have to take products
- zone_quantities = {}
+ routing_quantities = {}
for source_location, qty in move_lines.items():
- zone = pick_type_model._find_zone_for_location(source_location)
- zone_quantities.setdefault(zone, 0.0)
- zone_quantities[zone] += qty
-
- if len(zone_quantities) == 1:
- # The whole quantity can be taken from only one zone (a
- # non-zone being equal to a zone here), nothing to split.
+ routing_picking_type = \
+ source_location._find_picking_type_for_routing_operation()
+ routing_quantities.setdefault(routing_picking_type, 0.0)
+ routing_quantities[routing_picking_type] += qty
+
+ if len(routing_quantities) == 1:
+ # The whole quantity can be taken from only one location (an
+ # empty routing picking type being equal to one location here), nothing to split.
continue
move._do_unreserve()
move_to_assign_ids.add(move.id)
- zone_location = zone.default_location_src_id
- for zone, qty in zone_quantities.items():
- # if zone is False-ish, we take in a location which is
+ for picking_type, qty in routing_quantities.items():
+ # if picking type is empty, we don't need a new move
# not a zone
- if zone:
- # split returns the same move if the qty is the same
+ if picking_type:
+ routing_location = picking_type.default_location_src_id
+ # if we have a picking type, split returns the same move if the qty is the same
new_move_id = move._split(qty)
- new_move_per_location.setdefault(zone_location.id, [])
- new_move_per_location[zone_location.id].append(new_move_id)
+ new_move_per_location.setdefault(routing_location.id, [])
+ new_move_per_location[routing_location.id].append(
+ new_move_id
+ )
- # it is important to assign the zones first
+ # it is important to assign the routed moves first
for location_id, new_move_ids in new_move_per_location.items():
new_moves = self.browse(new_move_ids)
new_moves.with_context(
- # Prevent to call _apply_move_location_zone, will be called
+ # Prevent to call _apply_move_location_routing_operation, will be called
# when all lines are processed.
- exclude_apply_zone=True,
- # Force reservation of quants in the zone they were
+ exclude_apply_routing_operation=True,
+ # Force reservation of quants in the location they were
# reserved in at the origin (so we keep the same quantities
# at the same places)
gather_in_location_id=location_id,
@@ -78,33 +79,33 @@ def _split_per_zone(self):
))
return self + new_moves
- def _apply_move_location_zone(self):
+ def _apply_move_location_routing_operation(self):
for move in self:
if move.state not in ('assigned', 'partially_available'):
continue
- pick_type_model = self.env['stock.picking.type']
-
# Group move lines per source location, some may need an additional
# operations while others not. Store the number of products to
# take from each location, so we'll be able to split the move
# if needed.
- # 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
+ # At this point, we should not have lines with different source locations,
+ # they have been split in _split_per_routing_operation(), so we
+ # can take the first one
source = move.move_line_ids[0].location_id
- zone = pick_type_model._find_zone_for_location(source)
- if not zone:
+ picking_type = source._find_picking_type_for_routing_operation()
+ if not picking_type:
continue
- if (move.picking_type_id == zone and
- move.location_dest_id == zone.default_location_dest_id):
+ if (
+ move.picking_type_id == picking_type and
+ move.location_dest_id == picking_type.default_location_dest_id
+ ):
# already done
continue
move._do_unreserve()
move.write({
- 'location_dest_id': zone.default_location_dest_id.id,
- 'picking_type_id': zone.id,
+ 'location_dest_id': picking_type.default_location_dest_id.id,
+ 'picking_type_id': picking_type.id,
})
move._insert_middle_moves()
move._assign_picking()
diff --git a/stock_picking_type_routing_operation/models/stock_picking_type.py b/stock_picking_type_routing_operation/models/stock_picking_type.py
new file mode 100644
index 000000000000..6a270beb8959
--- /dev/null
+++ b/stock_picking_type_routing_operation/models/stock_picking_type.py
@@ -0,0 +1,45 @@
+# Copyright 2019 Camptocamp (https://www.camptocamp.com)
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+from odoo import _, api, exceptions, fields, models
+
+
+class StockPickingType(models.Model):
+ _inherit = 'stock.picking.type'
+
+ routing_operation_location_ids = fields.One2many(
+ 'stock.location', 'routing_operation_picking_type_id'
+ )
+
+ @api.constrains(
+ 'routing_operation_location_ids', 'default_location_src_id'
+ )
+ def _check_routing_operation_location_src_unique(self):
+ for picking_type in self:
+ if not picking_type.routing_operation_location_ids:
+ continue
+ if len(picking_type.routing_operation_location_ids):
+ raise exceptions.ValidationError(_(
+ 'The same picking type cannot be used on different '
+ 'locations having routing operations.'
+ ))
+ if (
+ picking_type.routing_operation_location_ids
+ != picking_type.default_location_src_id
+ ):
+ raise exceptions.ValidationError(_(
+ 'A picking type for routing operations cannot have a'
+ ' different default source location than the location it '
+ 'is used on.'
+ ))
+ src_location = picking_type.default_location_src_id
+ domain = [
+ ('routing_operation_location_ids', '!=', False),
+ ('default_location_src_id', '=', src_location.id),
+ ('id', '!=', picking_type.id)
+ ]
+ other = self.search(domain)
+ if other:
+ raise exceptions.ValidationError(
+ _('Another routing operation picking type (%s) exists for'
+ ' the same source location.') % (other.display_name,)
+ )
diff --git a/stock_picking_zone/models/stock_quant.py b/stock_picking_type_routing_operation/models/stock_quant.py
similarity index 97%
rename from stock_picking_zone/models/stock_quant.py
rename to stock_picking_type_routing_operation/models/stock_quant.py
index f076476ea91a..2700a987471a 100644
--- a/stock_picking_zone/models/stock_quant.py
+++ b/stock_picking_type_routing_operation/models/stock_quant.py
@@ -1,5 +1,5 @@
# Copyright 2019 Camptocamp (https://www.camptocamp.com)
-
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""Allow forcing reservations of quants in a location (or children)
When the context key "gather_in_location_id" is passed, it will look
diff --git a/stock_picking_zone/readme/CONFIGURE.rst b/stock_picking_type_routing_operation/readme/CONFIGURE.rst
similarity index 69%
rename from stock_picking_zone/readme/CONFIGURE.rst
rename to stock_picking_type_routing_operation/readme/CONFIGURE.rst
index fbb186440aa4..9e880ec0c5c6 100644
--- a/stock_picking_zone/readme/CONFIGURE.rst
+++ b/stock_picking_type_routing_operation/readme/CONFIGURE.rst
@@ -4,7 +4,7 @@ In Inventory Settings, you must have:
* Multi-Warehouses
* Multi-Step Routes
-Create an operation type and activate the "Is Zone" checkbox.
+On stock location, create an "Routing operation" operation type.
The default destination location will be the destination location
of the new operation inserted when a move has a source location which
-is a child of the type's source location.
+is a child of the location.
diff --git a/stock_picking_zone/readme/CONTRIBUTORS.rst b/stock_picking_type_routing_operation/readme/CONTRIBUTORS.rst
similarity index 69%
rename from stock_picking_zone/readme/CONTRIBUTORS.rst
rename to stock_picking_type_routing_operation/readme/CONTRIBUTORS.rst
index 63ccd231e8e3..7c15b32aa74c 100644
--- a/stock_picking_zone/readme/CONTRIBUTORS.rst
+++ b/stock_picking_type_routing_operation/readme/CONTRIBUTORS.rst
@@ -1,2 +1,3 @@
* Joël Grand-Guillaume
* Guewen Baconnier
+* Akim Juillerat
diff --git a/stock_picking_type_routing_operation/readme/DESCRIPTION.rst b/stock_picking_type_routing_operation/readme/DESCRIPTION.rst
new file mode 100644
index 000000000000..99e1cf4dc2e6
--- /dev/null
+++ b/stock_picking_type_routing_operation/readme/DESCRIPTION.rst
@@ -0,0 +1,10 @@
+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
diff --git a/stock_picking_zone/tests/__init__.py b/stock_picking_type_routing_operation/tests/__init__.py
similarity index 100%
rename from stock_picking_zone/tests/__init__.py
rename to stock_picking_type_routing_operation/tests/__init__.py
diff --git a/stock_picking_zone/tests/test_picking_zone.py b/stock_picking_type_routing_operation/tests/test_picking_zone.py
similarity index 96%
rename from stock_picking_zone/tests/test_picking_zone.py
rename to stock_picking_type_routing_operation/tests/test_picking_zone.py
index 670e523cf540..8d5020b121f6 100644
--- a/stock_picking_zone/tests/test_picking_zone.py
+++ b/stock_picking_type_routing_operation/tests/test_picking_zone.py
@@ -3,7 +3,7 @@
from odoo.tests import common
-class TestPickingZone(common.SavepointCase):
+class TestPickingTypeRoutingOperation(common.SavepointCase):
@classmethod
def setUpClass(cls):
@@ -56,16 +56,18 @@ def setUpClass(cls):
'padding': 5,
'company_id': cls.wh.company_id.id,
})
- cls.pick_type_zone = cls.env['stock.picking.type'].create({
- 'name': 'Zone',
+ cls.pick_type_routing_op = cls.env['stock.picking.type'].create({
+ 'name': 'Routing operation',
'code': 'internal',
'use_create_lots': False,
'use_existing_lots': True,
'default_location_src_id': cls.location_hb.id,
'default_location_dest_id': cls.location_handover.id,
- 'is_zone': True,
'sequence_id': picking_type_sequence.id,
})
+ cls.location_hb.write({
+ 'routing_operation_picking_type_id': cls.pick_type_routing_op.id
+ })
def _create_pick_ship(self, wh, products=None):
"""Create pick+ship pickings
@@ -156,7 +158,7 @@ def process_operations(self, move):
move.move_line_ids.qty_done = qty
move.picking_id.action_done()
- def test_change_location_to_zone(self):
+ def test_change_location_to_routing_operation(self):
pick_picking, customer_picking = self._create_pick_ship(
self.wh, [(self.product1, 10)]
)
@@ -173,7 +175,9 @@ def test_change_location_to_zone(self):
self.assert_src_highbay_1_2(ml)
self.assert_dest_handover(ml)
- self.assertEqual(ml.picking_id.picking_type_id, self.pick_type_zone)
+ self.assertEqual(
+ ml.picking_id.picking_type_id, self.pick_type_routing_op
+ )
self.assert_src_stock(move_a)
self.assert_dest_handover(move_a)
@@ -266,7 +270,9 @@ def test_several_moves(self):
self.assert_src_highbay_1_2(ml)
self.assert_dest_handover(ml)
# this is a new HO picking
- self.assertEqual(ml.picking_id.picking_type_id, self.pick_type_zone)
+ self.assertEqual(
+ ml.picking_id.picking_type_id, self.pick_type_routing_op
+ )
self.assertEqual(ml.state, 'assigned')
# this one stays in the PICK/
@@ -391,7 +397,9 @@ def test_several_move_lines(self):
self.assert_src_highbay_1_2(ml)
self.assert_dest_handover(ml)
# this is a new HO picking
- self.assertEqual(ml.picking_id.picking_type_id, self.pick_type_zone)
+ self.assertEqual(
+ ml.picking_id.picking_type_id, self.pick_type_routing_op
+ )
self.assertEqual(ml.state, 'assigned')
# the split move is waiting for 'move_ho'
diff --git a/stock_picking_type_routing_operation/views/stock_location.xml b/stock_picking_type_routing_operation/views/stock_location.xml
new file mode 100644
index 000000000000..9cf4c24b4a60
--- /dev/null
+++ b/stock_picking_type_routing_operation/views/stock_location.xml
@@ -0,0 +1,13 @@
+
+
+
+ stock.location.form.inherit
+ stock.location
+
+
+
+
+
+
+
+
diff --git a/stock_picking_zone/models/stock_picking_type.py b/stock_picking_zone/models/stock_picking_type.py
deleted file mode 100644
index 9842eac1ebe8..000000000000
--- a/stock_picking_zone/models/stock_picking_type.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2019 Camptocamp (https://www.camptocamp.com)
-
-from odoo import _, api, exceptions, fields, models
-
-
-class StockPickingType(models.Model):
- _inherit = 'stock.picking.type'
-
- is_zone = fields.Boolean(
- help="Change destination of the move line according to the"
- " default destination setup after reservation occurs",
- )
-
- @api.constrains('is_zone', 'default_location_src_id')
- def _check_zone_location_src_unique(self):
- for picking_type in self:
- if not picking_type.is_zone:
- continue
- src_location = picking_type.default_location_src_id
- domain = [
- ('is_zone', '=', True),
- ('default_location_src_id', '=', src_location.id),
- ('id', '!=', picking_type.id)
- ]
- other = self.search(domain)
- if other:
- raise exceptions.ValidationError(
- _('Another zone picking type (%s) exists for'
- ' the some source location.') % (other.display_name,)
- )
-
- @api.model
- def _find_zone_for_location(self, location):
- # First select all the parent locations and the matching
- # zones. In a second step, the zone matching the closest location
- # is searched in memory. This is to avoid doing an SQL query
- # for each location in the tree.
- tree = self.env['stock.location'].search(
- [('id', 'parent_of', location.id)],
- # the recordset will be ordered bottom location to top location
- order='parent_path desc'
- )
- zones = self.search([
- ('is_zone', '=', True),
- ('default_location_src_id', 'in', tree.ids)
- ])
- # the first location is the current move line's source location,
- # then we climb up the tree of locations
- for location in tree:
- match = [
- zone for zone in zones
- if zone.default_location_src_id == location
- ]
- if match:
- # we can only have one match as we have a unique
- # constraint on is_zone + source location
- return match[0]
- return self.browse()
diff --git a/stock_picking_zone/readme/DESCRIPTION.rst b/stock_picking_zone/readme/DESCRIPTION.rst
deleted file mode 100644
index da4e3a5e0d2b..000000000000
--- a/stock_picking_zone/readme/DESCRIPTION.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Route explains the steps you want to produce whereas the “picking zone” defines
-how operations are grouped according to their final source and destination
-location.
-
-This allows for example:
-
-* To parallelize picking operations in two main zone of a warehouse, splitting
- them in two different picking type
-* To define pre-picking (wave) in some sub-zones, then roundtrip picking of the
- sub-zone waves
diff --git a/stock_picking_zone/readme/ROADMAP.rst b/stock_picking_zone/readme/ROADMAP.rst
deleted file mode 100644
index ebc15a66e633..000000000000
--- a/stock_picking_zone/readme/ROADMAP.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-The concept of "zone" is duplicated with the zones introduced by
-"stock_location_zone" in
-https://github.com/OCA/stock-logistics-warehouse/pull/653
-They will be merged in the same concept. Note that considering the
-alpha version of this module, no data migration will be done.
diff --git a/stock_picking_zone/views/stock_picking_type_views.xml b/stock_picking_zone/views/stock_picking_type_views.xml
deleted file mode 100644
index cd57aa8a2d7c..000000000000
--- a/stock_picking_zone/views/stock_picking_type_views.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- Operation Types
- stock.picking.type
-
-
-
-
-
-
-
-
-