diff --git a/stock_picking_zone/__manifest__.py b/stock_picking_zone/__manifest__.py
index e86d9d487c88..8b4123dac55e 100644
--- a/stock_picking_zone/__manifest__.py
+++ b/stock_picking_zone/__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_zone/demo/stock_location_demo.xml
index d721e0a8f957..d29639c5d3f2 100644
--- a/stock_picking_zone/demo/stock_location_demo.xml
+++ b/stock_picking_zone/demo/stock_location_demo.xml
@@ -3,8 +3,7 @@
Highbay
-
+
Bay A
@@ -21,8 +20,7 @@
Handover
-
+
diff --git a/stock_picking_zone/models/__init__.py b/stock_picking_zone/models/__init__.py
index b2c5936e1170..200ba08be0a0 100644
--- a/stock_picking_zone/models/__init__.py
+++ b/stock_picking_zone/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_zone/models/stock_location.py b/stock_picking_zone/models/stock_location.py
new file mode 100644
index 000000000000..bf98759cccb1
--- /dev/null
+++ b/stock_picking_zone/models/stock_location.py
@@ -0,0 +1,44 @@
+# Copyright 2019 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
+from odoo import api, models, fields
+
+
+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",
+ # TODO add domain ?
+ )
+
+ @api.multi
+ def _find_picking_type_for_routing_operation(self):
+ self.ensure_one()
+ # 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.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_zone/models/stock_move.py
index a2afed1eaf67..8a553b903e28 100644
--- a/stock_picking_zone/models/stock_move.py
+++ b/stock_picking_zone/models/stock_move.py
@@ -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
@@ -33,28 +31,33 @@ def _split_per_zone(self):
# We'll split the move to have one move per different zones 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
+ 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(zone_quantities) == 1:
+ if len(routing_quantities) == 1:
# The whole quantity can be taken from only one zone (a
# non-zone being equal to a zone 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():
+ # FIXME routing_picking_type is the last element from loop above
+ # not sure it's correct
+ routing_location = routing_picking_type.default_location_src_id
+ for picking_type, qty in routing_quantities.items():
# if zone is False-ish, we take in a location which is
# not a zone
- if zone:
+ if 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
for location_id, new_move_ids in new_move_per_location.items():
@@ -62,7 +65,7 @@ def _split_per_zone(self):
new_moves.with_context(
# Prevent to call _apply_move_location_zone, will be called
# when all lines are processed.
- exclude_apply_zone=True,
+ exclude_apply_routing_operation=True,
# Force reservation of quants in the zone they were
# reserved in at the origin (so we keep the same quantities
# at the same places)
@@ -78,33 +81,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
+ # 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_zone/models/stock_picking_type.py b/stock_picking_zone/models/stock_picking_type.py
index 9842eac1ebe8..3776cb0b68e1 100644
--- a/stock_picking_zone/models/stock_picking_type.py
+++ b/stock_picking_zone/models/stock_picking_type.py
@@ -6,53 +6,40 @@
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",
+ routing_operation_location_ids = fields.One2many(
+ 'stock.location', 'routing_operation_picking_type_id'
)
- @api.constrains('is_zone', 'default_location_src_id')
- def _check_zone_location_src_unique(self):
+ @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.is_zone:
+ 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 = [
- ('is_zone', '=', True),
+ ('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 zone picking type (%s) exists for'
- ' the some source location.') % (other.display_name,)
+ _('Another routing operation picking type (%s) exists for'
+ ' the same 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/CONTRIBUTORS.rst b/stock_picking_zone/readme/CONTRIBUTORS.rst
index 63ccd231e8e3..7c15b32aa74c 100644
--- a/stock_picking_zone/readme/CONTRIBUTORS.rst
+++ b/stock_picking_zone/readme/CONTRIBUTORS.rst
@@ -1,2 +1,3 @@
* Joël Grand-Guillaume
* Guewen Baconnier
+* Akim Juillerat
diff --git a/stock_picking_zone/tests/test_picking_zone.py b/stock_picking_zone/tests/test_picking_zone.py
index 670e523cf540..8d5020b121f6 100644
--- a/stock_picking_zone/tests/test_picking_zone.py
+++ b/stock_picking_zone/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_zone/views/stock_location.xml b/stock_picking_zone/views/stock_location.xml
new file mode 100644
index 000000000000..8a4c6f86cfff
--- /dev/null
+++ b/stock_picking_zone/views/stock_location.xml
@@ -0,0 +1,13 @@
+
+
+
+ stock.location.form.inherit
+ stock.location
+
+
+
+
+
+
+
+