mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Add method on location to fetch a tray
* Add vertical_lift_shuttle_id field on stock.location, help to find the shuttle for a location * Add StockLocation.fetch_vertical_lift_tray(), that needs to be implemented in addons to send commands to the hardward to fetch a tray, and if existing show a cell (laser pointer, ...) * Add helpers on stock.move.line fetch_vertical_lift_tray_source() and fetch_vertical_lift_tray_dest() that fetch the tray directly from a move line's source or destination location
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
],
|
||||
'data': [
|
||||
'views/stock_location_views.xml',
|
||||
'views/stock_move_line_views.xml',
|
||||
'views/vertical_lift_shuttle_views.xml',
|
||||
'views/stock_vertical_lift_templates.xml',
|
||||
'views/shuttle_screen_templates.xml',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from . import vertical_lift_shuttle
|
||||
from . import stock_location
|
||||
from . import stock_move
|
||||
from . import stock_move_line
|
||||
from . import stock_quant
|
||||
|
||||
@@ -1,40 +1,156 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = "stock.location"
|
||||
|
||||
vertical_lift_location = fields.Boolean(
|
||||
'Is a Vertical Lift View Location?',
|
||||
"Is a Vertical Lift View Location?",
|
||||
default=False,
|
||||
help="Check this box to use it as the view for Vertical"
|
||||
" Lift Shuttles.",
|
||||
)
|
||||
vertical_lift_kind = fields.Selection(
|
||||
selection=[
|
||||
('view', 'View'),
|
||||
('shuttle', 'Shuttle'),
|
||||
('tray', 'Tray'),
|
||||
('cell', 'Cell'),
|
||||
("view", "View"),
|
||||
("shuttle", "Shuttle"),
|
||||
("tray", "Tray"),
|
||||
("cell", "Cell"),
|
||||
],
|
||||
compute='_compute_vertical_lift_kind',
|
||||
compute="_compute_vertical_lift_kind",
|
||||
store=True,
|
||||
)
|
||||
|
||||
# This is a one2one in practice, but this one is not really interesting.
|
||||
# It's there only to be in the depends of 'vertical_lift_shuttle_id', which
|
||||
# give the unique shuttle for any location in the tree (whether it's a
|
||||
# shuttle, a tray or a cell)
|
||||
inverse_vertical_lift_shuttle_ids = fields.One2many(
|
||||
comodel_name="vertical.lift.shuttle",
|
||||
inverse_name="location_id",
|
||||
readonly=True,
|
||||
)
|
||||
# compute the unique shuttle for any shuttle, tray or cell location, by
|
||||
# going through the parents
|
||||
vertical_lift_shuttle_id = fields.Many2one(
|
||||
comodel_name="vertical.lift.shuttle",
|
||||
compute="_compute_vertical_lift_shuttle_id",
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'location_id',
|
||||
'location_id.vertical_lift_kind',
|
||||
'vertical_lift_location',
|
||||
"location_id",
|
||||
"location_id.vertical_lift_kind",
|
||||
"vertical_lift_location",
|
||||
)
|
||||
def _compute_vertical_lift_kind(self):
|
||||
tree = {'view': 'shuttle', 'shuttle': 'tray', 'tray': 'cell'}
|
||||
tree = {"view": "shuttle", "shuttle": "tray", "tray": "cell"}
|
||||
for location in self:
|
||||
if location.vertical_lift_location:
|
||||
location.vertical_lift_kind = 'view'
|
||||
location.vertical_lift_kind = "view"
|
||||
continue
|
||||
kind = tree.get(location.location_id.vertical_lift_kind)
|
||||
if kind:
|
||||
location.vertical_lift_kind = kind
|
||||
|
||||
@api.depends(
|
||||
"inverse_vertical_lift_shuttle_ids",
|
||||
"location_id.vertical_lift_shuttle_id",
|
||||
)
|
||||
def _compute_vertical_lift_shuttle_id(self):
|
||||
for location in self:
|
||||
if location.inverse_vertical_lift_shuttle_ids:
|
||||
# we have a unique constraint on the other side
|
||||
assert len(location.inverse_vertical_lift_shuttle_ids) == 1
|
||||
shuttle = location.inverse_vertical_lift_shuttle_ids
|
||||
else:
|
||||
shuttle = location.location_id.vertical_lift_shuttle_id
|
||||
location.vertical_lift_shuttle_id = shuttle
|
||||
|
||||
def _hardware_vertical_lift_tray(self, cell_location=None):
|
||||
"""Send instructions to the vertical lift hardware
|
||||
|
||||
Private method, this is where the implementation actually happens.
|
||||
Addons can add their instructions based on the hardware used for
|
||||
this location.
|
||||
|
||||
The hardware used for a location can be found in:
|
||||
|
||||
``self.vertical_lift_shuttle_id.hardware``
|
||||
|
||||
Each addon can implement its own mechanism depending of this value
|
||||
and must call ``super``.
|
||||
|
||||
The method must send the command to the vertical lift to fetch / open
|
||||
the tray. If a ``cell_location`` is passed and if the hardware supports
|
||||
a way to show a cell (such as a laser pointer), it should send this
|
||||
command as well.
|
||||
|
||||
Useful information that could be needed for the drivers:
|
||||
|
||||
* Any field of `self` (name, barcode, ...) which is the current tray.
|
||||
* Any field of `cell_location` (name, barcode, ...) which is the cell
|
||||
in the tray.
|
||||
* ``self.vertical_lift_shuttle_id`` is the current Shuttle, where we
|
||||
find details about the hardware, the current mode (pick, put, ...).
|
||||
* ``self.tray_type_id`` is the kind of tray.
|
||||
* ``self.tray_type_id.width_per_cell`` and
|
||||
``self.tray_type_id.depth_per_cell`` return the size of a cell in mm.
|
||||
* ``cell_location.posx`` and ``posy`` are the coordinate from the
|
||||
bottom-left of the tray.
|
||||
* ``cell_location.tray_cell_center_position()`` returns the central
|
||||
position of the cell in mm from the bottom-left of a tray. (distance
|
||||
from left, distance from bottom). Can be used for instance for
|
||||
highlighting the cell using a laser pointer.
|
||||
"""
|
||||
if self.vertical_lift_shuttle_id.hardware == "simulation":
|
||||
message = _("Opening tray {}.").format(self.name)
|
||||
if cell_location:
|
||||
from_left, from_bottom = (
|
||||
cell_location.tray_cell_center_position()
|
||||
)
|
||||
message += _(
|
||||
"<br/>Laser pointer on x{} y{} ({}mm, {}mm)"
|
||||
).format(
|
||||
cell_location.posx,
|
||||
cell_location.posy,
|
||||
from_left,
|
||||
from_bottom,
|
||||
)
|
||||
self.env.user.notify_info(
|
||||
message=message, title=_("Lift Simulation")
|
||||
)
|
||||
|
||||
def fetch_vertical_lift_tray(self, cell_location=None):
|
||||
"""Send instructions to the vertical lift hardware
|
||||
|
||||
Public method to use for:
|
||||
* fetch the vertical lift tray and presenting it to the operator
|
||||
(physically)
|
||||
* direct the laser pointer to the cell's location if set
|
||||
|
||||
Depending on the hardware, the laser pointer may not be implemented.
|
||||
|
||||
The actual implementation of the method goes in the private method
|
||||
``_hardware_vertical_lift_tray()``.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.vertical_lift_kind == "cell":
|
||||
if cell_location:
|
||||
raise ValueError(
|
||||
"cell_location cannot be set when the location is "
|
||||
"a cell."
|
||||
)
|
||||
tray = self.location_id
|
||||
tray.fetch_vertical_lift_tray(cell_location=self)
|
||||
elif self.vertical_lift_kind == "tray":
|
||||
self._hardware_vertical_lift_tray(cell_location=cell_location)
|
||||
else:
|
||||
raise exceptions.UserError(
|
||||
_("Cannot fetch a vertical lift tray on location %s")
|
||||
% (self.name,)
|
||||
)
|
||||
return True
|
||||
|
||||
18
stock_vertical_lift/models/stock_move_line.py
Normal file
18
stock_vertical_lift/models/stock_move_line.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = "stock.move.line"
|
||||
|
||||
def fetch_vertical_lift_tray_source(self):
|
||||
self.ensure_one()
|
||||
self.location_id.fetch_vertical_lift_tray()
|
||||
return {"type": "ir.actions.do_nothing"}
|
||||
|
||||
def fetch_vertical_lift_tray_dest(self):
|
||||
self.ensure_one()
|
||||
self.location_dest_id.fetch_vertical_lift_tray()
|
||||
return {"type": "ir.actions.do_nothing"}
|
||||
@@ -107,6 +107,11 @@ class VerticalLiftShuttle(models.Model):
|
||||
store=False,
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
('location_id_unique', 'UNIQUE(location_id)',
|
||||
'You cannot have two shuttles using the same location.'),
|
||||
]
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
# FIXME notify_info is only for the demo
|
||||
@@ -258,9 +263,6 @@ class VerticalLiftShuttle(models.Model):
|
||||
)
|
||||
|
||||
def button_release(self):
|
||||
if self.current_move_line_id:
|
||||
self._hardware_switch_off_laser_pointer()
|
||||
self._hardware_close_tray()
|
||||
self.select_next_move_line()
|
||||
if not self.current_move_line_id:
|
||||
# sorry not sorry
|
||||
@@ -310,8 +312,8 @@ class VerticalLiftShuttle(models.Model):
|
||||
)
|
||||
self.operation_descr = descr
|
||||
if next_move_line:
|
||||
self._hardware_switch_on_laser_pointer()
|
||||
self._hardware_open_tray()
|
||||
# TODO different method (source vs dest) on pick/put scenario
|
||||
next_move_line.fetch_vertical_lift_tray_source()
|
||||
|
||||
def action_open_screen(self):
|
||||
self.select_next_move_line()
|
||||
@@ -365,36 +367,6 @@ class VerticalLiftShuttle(models.Model):
|
||||
self.mode = 'inventory'
|
||||
self.select_next_move_line()
|
||||
|
||||
def _hardware_switch_on_laser_pointer(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Laser pointer on x{} y{}').format(
|
||||
self.tray_x, self.tray_y
|
||||
),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_switch_off_laser_pointer(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Switch off laser pointer'),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_open_tray(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Opening tray {}').format(self.tray_name),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_close_tray(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Closing tray {}').format(self.tray_name),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
|
||||
class VerticalLiftShuttleManualBarcode(models.TransientModel):
|
||||
_name = 'vertical.lift.shuttle.manual.barcode'
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
attrs="{'invisible': [('vertical_lift_kind', '!=', False), ('vertical_lift_kind', '!=', 'view')]}"
|
||||
/>
|
||||
<field name="vertical_lift_kind"/>
|
||||
<field name="vertical_lift_shuttle_id"
|
||||
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('shuttle', 'tray', 'cell'))]}" />
|
||||
</field>
|
||||
<field name="tray_type_id" position="attributes">
|
||||
<attribute name="attrs">
|
||||
@@ -28,6 +30,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="tray_type_id" position="after">
|
||||
<field name="vertical_lift_kind"/>
|
||||
<field name="vertical_lift_shuttle_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
30
stock_vertical_lift/views/stock_move_line_views.xml
Normal file
30
stock_vertical_lift/views/stock_move_line_views.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_stock_move_line_operation_tree" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.operations.tree.tray.type</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<field name="inherit_id" ref="stock_location_tray.view_stock_move_line_operation_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<button name="action_show_source_tray" position="before">
|
||||
<button name="fetch_vertical_lift_tray_source"
|
||||
string="Fetch Source Tray"
|
||||
type="object" icon="fa-hand-paper-o"
|
||||
attrs="{'invisible': [('tray_source_matrix', '=', {})]}"
|
||||
invisible="not context.get('show_source_location')"
|
||||
groups="stock.group_stock_multi_locations"
|
||||
/>
|
||||
</button>
|
||||
<button name="action_show_dest_tray" position="before">
|
||||
<button name="fetch_vertical_lift_tray_dest"
|
||||
string="Fetch Destination Tray"
|
||||
type="object" icon="fa-hand-paper-o"
|
||||
attrs="{'invisible': [('tray_dest_matrix', '=', {})]}"
|
||||
invisible="not context.get('show_destination_location')"
|
||||
groups="stock.group_stock_multi_locations"
|
||||
/>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1 +1,2 @@
|
||||
from . import stock_location
|
||||
from . import vertical_lift_shuttle
|
||||
|
||||
56
stock_vertical_lift_kardex/models/stock_location.py
Normal file
56
stock_vertical_lift_kardex/models/stock_location.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from odoo import models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = 'stock.location'
|
||||
|
||||
def _hardware_kardex_prepare_payload(self, cell_location=None):
|
||||
return ""
|
||||
|
||||
def _hardware_vertical_lift_tray(self, cell_location=None):
|
||||
"""Send instructions to the vertical lift hardware
|
||||
|
||||
Private method, this is where the implementation actually happens.
|
||||
Addons can add their instructions based on the hardware used for
|
||||
this location.
|
||||
|
||||
The hardware used for a location can be found in:
|
||||
|
||||
``self.vertical_lift_shuttle_id.hardware``
|
||||
|
||||
Each addon can implement its own mechanism depending of this value
|
||||
and must call ``super``.
|
||||
|
||||
The method must send the command to the vertical lift to fetch / open
|
||||
the tray. If a ``cell_location`` is passed and if the hardware supports
|
||||
a way to show a cell (such as a laser pointer), it should send this
|
||||
command as well.
|
||||
|
||||
Useful information that could be needed for the drivers:
|
||||
|
||||
* Any field of `self` (name, barcode, ...) which is the current tray.
|
||||
* Any field of `cell_location` (name, barcode, ...) which is the cell
|
||||
in the tray.
|
||||
* ``self.vertical_lift_shuttle_id`` is the current Shuttle, where we
|
||||
find details about the hardware, the current mode (pick, put, ...).
|
||||
* ``self.tray_type_id`` is the kind of tray.
|
||||
* ``self.tray_type_id.width_per_cell`` and
|
||||
``self.tray_type_id.depth_per_cell`` return the size of a cell in mm.
|
||||
* ``cell_location.posx`` and ``posy`` are the coordinate from the
|
||||
bottom-left of the tray.
|
||||
* ``cell_location.tray_cell_center_position()`` returns the central
|
||||
position of the cell in mm from the bottom-left of a tray. (distance
|
||||
from left, distance from bottom). Can be used for instance for
|
||||
highlighting the cell using a laser pointer.
|
||||
"""
|
||||
if self.vertical_lift_shuttle_id.hardware == "kardex":
|
||||
payload = self._hardware_kardex_prepare_payload()
|
||||
_logger.debug("Sending to kardex: {}", payload)
|
||||
# TODO implement the communication with kardex
|
||||
super()._hardware_vertical_lift_tray(cell_location=cell_location)
|
||||
Reference in New Issue
Block a user