Rework stock_location_zone

This commit is contained in:
Akim Juillerat
2019-09-11 12:04:32 +02:00
committed by Denis Roussel
parent 5367dff997
commit a615a7a265
10 changed files with 98 additions and 223 deletions

View File

@@ -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',

View File

@@ -1,2 +1 @@
from . import stock_picking_zone
from . import stock_location

View File

@@ -3,103 +3,92 @@
# 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,
)
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')
location_kind = fields.Selection(
[
('zone', 'Zone'),
('area', 'Area'),
('bin', 'Bin'),
('stock', 'Main Stock'),
('other', 'Other'),
],
string='Location Kind',
compute='_compute_location_kind',
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',
)
area = fields.Char(
'Area',
compute='_compute_area', store=True,
oldname='zone')
_sql_constraints = [(
'name_zone_unique',
'EXCLUDE (name WITH =, zone_location_id WITH =)'
' WHERE (zone_location_id IS NOT NULL)',
'Another location with the same name exists in the same zone.'
' Please rename the location.'
)]
@api.depends('name', 'kind', 'location_id.area')
def _compute_area(self):
@api.depends('is_zone', 'usage', 'location_id.usage', 'zone_location_id',
'child_ids')
def _compute_location_kind(self):
for location in self:
if location.kind == 'area':
location.area = location.name
if location.is_zone:
location.location_kind = 'zone'
continue
# Internal locations whose parent is view are main stocks
if (
location.usage == 'internal'
and location.location_id.usage == 'view'
):
location.location_kind = 'stock'
continue
# Internal locations having a zone and no children are bins
if (
location.usage == 'internal'
and location.zone_location_id
and not location.child_ids
):
location.location_kind = 'bin'
continue
# Internal locations having a zone and children are areas
if (
location.usage == 'internal'
and location.zone_location_id
and location.child_ids
):
location.location_kind = 'area'
continue
# All the rest are other locations
location.location_kind = 'other'
@api.depends('is_zone', 'location_id.zone_location_id')
def _compute_zone_location_id(self):
for location in self:
if location.is_zone:
location.zone_location_id = location
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}'")
@api.multi
@api.onchange('corridor', 'row', 'rack', 'level',
'posx', 'posy', 'posz')
def _compute_name(self):
for location in self:
if not location.kind == 'bin':
continue
area = location
while area and not area.location_name_format:
area = area.location_id
if not area:
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)
location.zone_location_id = \
location.location_id.zone_location_id
@api.multi
@api.returns('self', lambda value: value.id)
@@ -107,38 +96,5 @@ class StockLocation(models.Model):
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()

View File

@@ -1,26 +0,0 @@
# Copyright 2017 Syvain Van Hoof (Okia sprl) <sylvainvh@okia.be>
# Copyright 2017-2019 Jacques-Etienne Baudoux (BCIM sprl) <je@bcim.be>
# 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',
)
]

View File

@@ -1,5 +0,0 @@
In Inventory Settings, you must have:
* Storage Locations
Set coordinate attibute on the locations.

View File

@@ -1,3 +1,4 @@
* Syvain Van Hoof (Okia sprl) <sylvainvh@okia.be>
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
* Guewen Baconnier (Camptocamp) <guewen.baconnier@camptocamp.com>
* Akim Juillerat <akim.juillerat@camptocamp.com>

View File

@@ -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
* 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

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_picking_zone access_picking_zone model_stock_picking_zone base.group_user 1 0 0 0
3 access_picking_zone_manager access_picking_zone_manager model_stock_picking_zone base.group_system 1 1 1 1

View File

@@ -6,22 +6,13 @@
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_form"/>
<field name="arch" type="xml">
<field name="scrap_location" position="before">
<field name="is_zone" />
</field>
<field name="usage" position="after">
<field name="kind" />
<field name="location_name_format" attrs="{'invisible': [('kind', '=', 'bin')]}" />
<field name="picking_type_id" options="{'no_create': True}" attrs="{'readonly': [('kind', '!=', 'zone')]}" />
<field name="location_kind" />
<field name="zone_location_id" />
</field>
<field name="posx" position="before">
<field name="picking_zone_id" />
<field name="area" />
<field name="corridor" />
<field name="row" />
<field name="rack" />
<field name="level" />
</field>
</field>
</record>
@@ -30,17 +21,15 @@
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_search"/>
<field name="arch" type="xml">
<xpath expr="//search" position="inside">
<field name="kind"/>
<field name="picking_zone_id"/>
<field name="area" />
<field name="corridor" />
<field name="row" />
<field name="rack" />
<field name="level" />
<field name="location_kind"/>
<field name="is_zone" />
<field name="zone_location_id" />
<group expand="0" string="Group By">
<filter string="Location Kind" name="location_kind" domain="[]" context="{'group_by':'location_kind'}"/>
<filter string="Zone location" name="zone_location" domain="[]" context="{'group_by':'zone_location_id'}"/>
</group>
</xpath>
</field>
</record>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="picking_zone_view_filter" model="ir.ui.view">
<field name="name">stock.picking.zone.view.filter</field>
<field name="model">stock.picking.zone</field>
<field name="arch" type="xml">
<search string="Picking Zones">
<field name="name"/>
<field name="code"/>
</search>
</field>
</record>
<record id="picking_zone_view_tree" model="ir.ui.view">
<field name="name">stock.picking.zone.view.tree</field>
<field name="model">stock.picking.zone</field>
<field name="arch" type="xml">
<tree string="Picking Zones" editable="bottom">
<field name="name"/>
<field name="code"/>
<field name="picking_type_id"/>
</tree>
</field>
</record>
<record id="action_picking_zone" model="ir.actions.act_window">
<field name="name">Picking Zones</field>
<field name="res_model">stock.picking.zone</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define the picking zones of your warehouses
</p>
</field>
</record>
<menuitem id="picking_zone_menu"
action="action_picking_zone"
parent="stock.menu_warehouse_config"
sequence="2"/>
</odoo>