mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Rework stock_location_zone
This commit is contained in:
committed by
Denis Roussel
parent
5367dff997
commit
a615a7a265
@@ -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',
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from . import stock_picking_zone
|
||||
from . import stock_location
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
In Inventory Settings, you must have:
|
||||
|
||||
* Storage Locations
|
||||
|
||||
Set coordinate attibute on the locations.
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user