diff --git a/stock_vertical_lift/__manifest__.py b/stock_vertical_lift/__manifest__.py
index e259e71f4..580fc0706 100644
--- a/stock_vertical_lift/__manifest__.py
+++ b/stock_vertical_lift/__manifest__.py
@@ -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',
diff --git a/stock_vertical_lift/models/__init__.py b/stock_vertical_lift/models/__init__.py
index 2a0e1d40b..80b7355db 100644
--- a/stock_vertical_lift/models/__init__.py
+++ b/stock_vertical_lift/models/__init__.py
@@ -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
diff --git a/stock_vertical_lift/models/stock_location.py b/stock_vertical_lift/models/stock_location.py
index a8d19e0a0..93e02cf65 100644
--- a/stock_vertical_lift/models/stock_location.py
+++ b/stock_vertical_lift/models/stock_location.py
@@ -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 += _(
+ " 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
diff --git a/stock_vertical_lift/models/stock_move_line.py b/stock_vertical_lift/models/stock_move_line.py
new file mode 100644
index 000000000..a26873c0a
--- /dev/null
+++ b/stock_vertical_lift/models/stock_move_line.py
@@ -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"}
diff --git a/stock_vertical_lift/models/vertical_lift_shuttle.py b/stock_vertical_lift/models/vertical_lift_shuttle.py
index 54ec752d2..fcdb4a3bd 100644
--- a/stock_vertical_lift/models/vertical_lift_shuttle.py
+++ b/stock_vertical_lift/models/vertical_lift_shuttle.py
@@ -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'
diff --git a/stock_vertical_lift/views/stock_location_views.xml b/stock_vertical_lift/views/stock_location_views.xml
index 930d7a179..62c6e2996 100644
--- a/stock_vertical_lift/views/stock_location_views.xml
+++ b/stock_vertical_lift/views/stock_location_views.xml
@@ -11,6 +11,8 @@
attrs="{'invisible': [('vertical_lift_kind', '!=', False), ('vertical_lift_kind', '!=', 'view')]}"
/>
+
@@ -28,6 +30,7 @@
+
diff --git a/stock_vertical_lift/views/stock_move_line_views.xml b/stock_vertical_lift/views/stock_move_line_views.xml
new file mode 100644
index 000000000..39249814e
--- /dev/null
+++ b/stock_vertical_lift/views/stock_move_line_views.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ stock.move.line.operations.tree.tray.type
+ stock.move.line
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_vertical_lift_kardex/models/__init__.py b/stock_vertical_lift_kardex/models/__init__.py
index e99db92f6..51a3830f6 100644
--- a/stock_vertical_lift_kardex/models/__init__.py
+++ b/stock_vertical_lift_kardex/models/__init__.py
@@ -1 +1,2 @@
+from . import stock_location
from . import vertical_lift_shuttle
diff --git a/stock_vertical_lift_kardex/models/stock_location.py b/stock_vertical_lift_kardex/models/stock_location.py
new file mode 100644
index 000000000..d1098d59a
--- /dev/null
+++ b/stock_vertical_lift_kardex/models/stock_location.py
@@ -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)