diff --git a/stock_location_zone/__manifest__.py b/stock_location_zone/__manifest__.py index 3ed81d847c12..c1249a3522d4 100644 --- a/stock_location_zone/__manifest__.py +++ b/stock_location_zone/__manifest__.py @@ -7,16 +7,13 @@ 'version': '12.0.1.0.0', 'author': "BCIM, Okia, Camptocamp, Odoo Community Association (OCA)", 'website': "https://github.com/OCA/stock-logistics-warehouse", - 'summary': "Add coordinate attributes on stock location. " - "Define picking zone with links to picking type.", + 'summary': "Classify locations with zones.", 'category': 'Stock Management', 'depends': [ 'stock', ], 'data': [ - 'views/stock_picking_zone.xml', 'views/stock_location.xml', - 'security/ir.model.access.csv', ], 'installable': True, 'development_status': 'Alpha', diff --git a/stock_location_zone/models/__init__.py b/stock_location_zone/models/__init__.py index 408b07ec6496..88493e35d945 100644 --- a/stock_location_zone/models/__init__.py +++ b/stock_location_zone/models/__init__.py @@ -1,2 +1 @@ -from . import stock_picking_zone from . import stock_location diff --git a/stock_location_zone/models/stock_location.py b/stock_location_zone/models/stock_location.py index d1bee86ce976..a7628b80371d 100644 --- a/stock_location_zone/models/stock_location.py +++ b/stock_location_zone/models/stock_location.py @@ -3,103 +3,105 @@ # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from psycopg2 import sql - -from odoo import _, api, fields, models, SUPERUSER_ID -from odoo.tools.sql import index_exists, _schema - - -def create_unique_index_where(cr, indexname, tablename, expressions, where): - """Create the given unique index unless it exists.""" - if index_exists(cr, indexname): - return - - args = ', '.join(expressions) - # pylint: disable=sql-injection - cr.execute( - sql.SQL( - 'CREATE UNIQUE INDEX {} ON {} ({}) WHERE {}').format( - sql.Identifier(indexname), - sql.Identifier(tablename), - sql.SQL(args), - sql.SQL(where), - ) - ) - _schema.debug( - "Table %r: created unique index %r (%s) WHERE {}", - tablename, indexname, args, where - ) +from odoo import api, fields, models, _ class StockLocation(models.Model): _inherit = 'stock.location' - # FIXME: add in selection: shuttle, tray (module vertical lift) - kind = fields.Selection([ - ('zone', 'Picking Zone'), - ('area', 'Area'), - ('bin', 'Bin')], - string='Kind') + is_zone = fields.Boolean( + string='Is a Zone Location?', + help='Mark to define this location as a zone', + ) - picking_zone_id = fields.Many2one( - 'stock.picking.zone', - string='Picking zone', + zone_location_id = fields.Many2one( + 'stock.location', + string='Location Zone', + compute='_compute_zone_location_id', + store=True, index=True, ) + area_location_id = fields.Many2one( + 'stock.location', + string='Location Area', + compute='_compute_zone_location_id', + store=True, + ) - picking_type_id = fields.Many2one( - related='picking_zone_id.picking_type_id', - help="Picking type for operations from this location", - oldname='barcode_picking_type_id') - - area = fields.Char( - 'Area', - compute='_compute_area', store=True, - oldname='zone') - - @api.depends('name', 'kind', 'location_id.area') - def _compute_area(self): - for location in self: - if location.kind == 'area': - location.area = location.name - else: - location.area = location.location_id.area - - corridor = fields.Char('Corridor', help="Street") - row = fields.Char('Row', help="Side in the street") - rack = fields.Char('Rack', oldname='shelf', help="House number") - level = fields.Char('Level', help="Height on the shelf") - posx = fields.Integer('Box (X)') - posy = fields.Integer('Box (Y)') - posz = fields.Integer('Box (Z)') - - location_name_format = fields.Char( - 'Location Name Format', - help="Format string that will compute the name of the location. " - "Use location fields. Example: " - "'{area}-{corridor:0>2}.{rack:0>3}" - ".{level:0>2}'") + location_kind = fields.Selection( + [ + ('zone', 'Zone'), + ('area', 'Area'), + ('bin', 'Bin'), + ('stock', 'Main Stock'), + ('other', 'Other'), + ], + string='Location Kind', + compute='_compute_location_kind', + store=True, + help='Group location according to their kinds: ' + '* Zone: locations that are flagged as being zones ' + '* Area: locations with children that are part of a zone ' + '* Bin: locations without children that are part of a zone ' + '* Stock: internal locations whose parent is a view ' + '* Other: any other location', + ) - @api.multi - @api.onchange('corridor', 'row', 'rack', 'level', - 'posx', 'posy', 'posz') - def _compute_name(self): + @api.depends('is_zone', 'location_id.zone_location_id', + 'location_id.area_location_id') + def _compute_zone_location_id(self): for location in self: - if not location.kind == 'bin': + location.zone_location_id = self.browse() + location.area_location_id = self.browse() + if location.is_zone: + location.zone_location_id = location continue - area = location - while area and not area.location_name_format: - area = area.location_id - if not area: + parent = location.location_id + if parent.zone_location_id: + location.zone_location_id = parent.zone_location_id + # If we have more than one level of area in a zone, + # the grouping is done by the first level + if parent.area_location_id: + location.area_location_id = parent.area_location_id + else: + location.area_location_id = location + + @api.depends('usage', 'location_id.usage', + 'child_ids', + 'area_location_id', + 'zone_location_id') + def _compute_location_kind(self): + for location in self: + if location.zone_location_id and not location.area_location_id: + location.location_kind = 'zone' continue - template = area.location_name_format - # We don't want to use the full browse record as it would - # give too much access to internals for the users. - # We cannot use location.read() as we may have a NewId. - # We should have the record's values in the cache at this - # point. We must be cautious not to leak an environment through - # relational fields. - location.name = template.format(**location._cache) + + parent = location.location_id + if ( + location.usage == 'internal' + and parent.usage == 'view' + ): + # Internal locations whose parent is view are main stocks + location.location_kind = 'stock' + elif ( + # Internal locations having a zone and no children are bins + location.usage == 'internal' + and location.zone_location_id + and location.area_location_id + and not location.child_ids + ): + location.location_kind = 'bin' + elif ( + location.usage == 'internal' + and location.zone_location_id + and location.area_location_id + and location.child_ids + ): + # Internal locations having a zone and children are areas + location.location_kind = 'area' + else: + # All the rest are other locations + location.location_kind = 'other' @api.multi @api.returns('self', lambda value: value.id) @@ -107,38 +109,5 @@ def copy(self, default=None): self.ensure_one() default = dict(default or {}) if 'name' not in default: - default['name'] = _("%s (copy)") % (self.name) + default['name'] = _("%s (copy)") % self.name return super().copy(default=default) - - @api.model_cr - def init(self): - env = api.Environment(self._cr, SUPERUSER_ID, {}) - self._init_zone_index(env) - - def _init_zone_index(self, env): - """Add unique index on name per zone - - We cannot use _sql_constraints because it doesn't support - WHERE conditions. We need to apply the unique constraint - only within the same zone, otherwise the constraint fails - even on demo data (locations created automatically for - warehouses). - """ - index_name = 'stock_location_unique_name_zone_index' - create_unique_index_where( - env.cr, index_name, self._table, - ['name', 'picking_zone_id'], - 'picking_zone_id IS NOT NULL' - ) - - @classmethod - def _init_constraints_onchanges(cls): - # As the unique index created in this model acts as a unique - # constraints but cannot be registered in '_sql_constraints' - # (it doesn't support WHERE clause), associate an error - # message manually (reproduce what _sql_constraints does). - key = 'unique_name_zone' - message = ('Another location with the same name exists in the same' - ' zone. Please rename the location.') - cls.pool._sql_error[cls._table + '_' + key] = message - super()._init_constraints_onchanges() diff --git a/stock_location_zone/models/stock_picking_zone.py b/stock_location_zone/models/stock_picking_zone.py deleted file mode 100644 index 57d586c11f9b..000000000000 --- a/stock_location_zone/models/stock_picking_zone.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2017 Syvain Van Hoof (Okia sprl) -# Copyright 2017-2019 Jacques-Etienne Baudoux (BCIM sprl) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import fields, models - - -class PickingZone(models.Model): - _name = 'stock.picking.zone' - _description = "Stock Picking Zone" - - name = fields.Char('Name', required=True, translate=True) - code = fields.Char('Code', required=True) - picking_type_id = fields.Many2one( - 'stock.picking.type', - string='Pick Type', - help="Picking type for operations from this location", - ) - - _sql_constraints = [ - ( - 'unique_picking_zone', - 'unique (code)', - 'The picking zone code must be unique', - ) - ] diff --git a/stock_location_zone/readme/CONFIGURE.rst b/stock_location_zone/readme/CONFIGURE.rst deleted file mode 100644 index 1d20f0c1ce78..000000000000 --- a/stock_location_zone/readme/CONFIGURE.rst +++ /dev/null @@ -1,5 +0,0 @@ -In Inventory Settings, you must have: - - * Storage Locations - -Set coordinate attibute on the locations. diff --git a/stock_location_zone/readme/CONTRIBUTORS.rst b/stock_location_zone/readme/CONTRIBUTORS.rst index 8ef4d90eb11c..0425dda611ce 100644 --- a/stock_location_zone/readme/CONTRIBUTORS.rst +++ b/stock_location_zone/readme/CONTRIBUTORS.rst @@ -1,3 +1,4 @@ * Syvain Van Hoof (Okia sprl) * Jacques-Etienne Baudoux (BCIM) * Guewen Baconnier (Camptocamp) +* Akim Juillerat diff --git a/stock_location_zone/readme/DESCRIPTION.rst b/stock_location_zone/readme/DESCRIPTION.rst index ba44b8538e7e..7b3ca7af2518 100644 --- a/stock_location_zone/readme/DESCRIPTION.rst +++ b/stock_location_zone/readme/DESCRIPTION.rst @@ -1,2 +1,10 @@ -Add coordinate attributes on stock location. -Define picking zone with links to picking type. +This module introduces Zone concept on stock locations to allow better +classification of stock locations in a warehouse. + +Locations are then classified by location kinds that could be: + +* Zone: locations that are flagged as being zones. Zones are subdivisions of the warehouse for splitting picking operations. A picking operator work in a zone and can pick from any location of the zone. +* Area: locations with children that are part of a zone. Areas are subdivisions of the warehouse for put-away operations. Each area has storage characteristics and strategy, e.g. area for pallets, shelf for boxes... +* Bin: locations without children that are part of a zone +* Stock: internal locations whose parent is a view +* Other: any other location diff --git a/stock_location_zone/security/ir.model.access.csv b/stock_location_zone/security/ir.model.access.csv deleted file mode 100644 index af447933955b..000000000000 --- a/stock_location_zone/security/ir.model.access.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_picking_zone,access_picking_zone,model_stock_picking_zone,base.group_user,1,0,0,0 -access_picking_zone_manager,access_picking_zone_manager,model_stock_picking_zone,base.group_system,1,1,1,1 diff --git a/stock_location_zone/views/stock_location.xml b/stock_location_zone/views/stock_location.xml index aa2fdd5b2900..e5624822bec7 100644 --- a/stock_location_zone/views/stock_location.xml +++ b/stock_location_zone/views/stock_location.xml @@ -6,22 +6,14 @@ stock.location - - - - - + + - - - - - - - - + + + + - @@ -30,17 +22,17 @@ stock.location - - - - - - - - + + + + + + + + + - diff --git a/stock_location_zone/views/stock_picking_zone.xml b/stock_location_zone/views/stock_picking_zone.xml deleted file mode 100644 index 9ac330587a81..000000000000 --- a/stock_location_zone/views/stock_picking_zone.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - stock.picking.zone.view.filter - stock.picking.zone - - - - - - - - - stock.picking.zone.view.tree - stock.picking.zone - - - - - - - - - - - Picking Zones - stock.picking.zone - ir.actions.act_window - form - -

- Define the picking zones of your warehouses -

-
-
- - -