Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WMS][12.0] rework stock_location_zone #711

Merged
merged 10 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions stock_location_zone/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 0 additions & 1 deletion stock_location_zone/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
from . import stock_picking_zone
from . import stock_location
205 changes: 87 additions & 118 deletions stock_location_zone/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,142 +3,111 @@
# 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',
)
guewen marked this conversation as resolved.
Show resolved Hide resolved

@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)
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()
26 changes: 0 additions & 26 deletions stock_location_zone/models/stock_picking_zone.py

This file was deleted.

5 changes: 0 additions & 5 deletions stock_location_zone/readme/CONFIGURE.rst

This file was deleted.

1 change: 1 addition & 0 deletions stock_location_zone/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Syvain Van Hoof (Okia sprl) <[email protected]>
* Jacques-Etienne Baudoux (BCIM) <[email protected]>
* Guewen Baconnier (Camptocamp) <[email protected]>
* Akim Juillerat <[email protected]>
12 changes: 10 additions & 2 deletions stock_location_zone/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -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
grindtildeath marked this conversation as resolved.
Show resolved Hide resolved
* Area: locations with children that are part of a zone
grindtildeath marked this conversation as resolved.
Show resolved Hide resolved
* Bin: locations without children that are part of a zone
* Stock: internal locations whose parent is a view
* Other: any other location
3 changes: 0 additions & 3 deletions stock_location_zone/security/ir.model.access.csv

This file was deleted.

38 changes: 15 additions & 23 deletions stock_location_zone/views/stock_location.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,14 @@
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock.view_location_form"/>
<field name="arch" type="xml">

<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="scrap_location" position="before">
<field name="is_zone" />
</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 name="usage" position="after">
<field name="location_kind" />
<field name="zone_location_id" />
<field name="area_location_id" />
</field>

</field>
</record>

Expand All @@ -30,17 +22,17 @@
<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" />
<field name="area_location_id" />
<group expand="0" string="Group By">
<filter string="Location Kind" name="location_kind" domain="[]" context="{'group_by':'location_kind'}"/>
guewen marked this conversation as resolved.
Show resolved Hide resolved
<filter string="Zone location" name="zone_location" domain="[]" context="{'group_by':'zone_location_id'}"/>
<filter string="Area location" name="area_location" domain="[]" context="{'group_by':'area_location_id'}"/>
</group>
</xpath>

</field>
</record>

Expand Down
Loading