mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Add release (close) of vertical lift trays
* Rename methods that fetch a tray to prevent confusion * Add methods to release a tray * The Kardex method to fetch a tray has to send "0" in the carrier and carrierNext field * The pick and inventory screens release the tray only when there is no next line, because the release is implicit when we fetch the next line, the put screen releases everytime because the operator may take time to start the next line and we don't know if they are going to scan a next line or not. * Exiting the screen or switching screen between put/pick/put-away has to release the tray as well.
This commit is contained in:
@@ -63,12 +63,12 @@ class StockLocation(models.Model):
|
||||
shuttle = location.location_id.vertical_lift_shuttle_id
|
||||
location.vertical_lift_shuttle_id = shuttle
|
||||
|
||||
def _hardware_vertical_lift_tray(self, cell_location=None):
|
||||
payload = self._hardware_vertical_lift_tray_payload(cell_location)
|
||||
def _hardware_vertical_lift_fetch_tray(self, cell_location=None):
|
||||
payload = self._hardware_vertical_lift_fetch_tray_payload(cell_location)
|
||||
return self.vertical_lift_shuttle_id._hardware_send_message(payload)
|
||||
|
||||
def _hardware_vertical_lift_tray_payload(self, cell_location=None):
|
||||
"""Prepare the message to be sent to the vertical lift hardware
|
||||
def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
|
||||
"""Prepare "fetch" message to be sent 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
|
||||
@@ -118,7 +118,7 @@ class StockLocation(models.Model):
|
||||
raise NotImplementedError()
|
||||
|
||||
def fetch_vertical_lift_tray(self, cell_location=None):
|
||||
"""Send instructions to the vertical lift hardware
|
||||
"""Send instructions to the vertical lift hardware to fetch a tray
|
||||
|
||||
Public method to use for:
|
||||
* fetch the vertical lift tray and presenting it to the operator
|
||||
@@ -128,7 +128,7 @@ class StockLocation(models.Model):
|
||||
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()``.
|
||||
``_hardware_vertical_lift_fetch_tray()``.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.vertical_lift_kind == "cell":
|
||||
@@ -139,7 +139,7 @@ class StockLocation(models.Model):
|
||||
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)
|
||||
self._hardware_vertical_lift_fetch_tray(cell_location=cell_location)
|
||||
else:
|
||||
raise exceptions.UserError(
|
||||
_("Cannot fetch a vertical lift tray on location %s") % (self.name,)
|
||||
@@ -151,3 +151,9 @@ class StockLocation(models.Model):
|
||||
if self.vertical_lift_kind in ("cell", "tray"):
|
||||
self.fetch_vertical_lift_tray()
|
||||
return True
|
||||
|
||||
def button_release_vertical_lift_tray(self):
|
||||
self.ensure_one()
|
||||
if self.vertical_lift_kind:
|
||||
self.vertical_lift_shuttle_id.release_vertical_lift_tray()
|
||||
return True
|
||||
|
||||
@@ -264,5 +264,7 @@ class VerticalLiftOperationInventory(models.Model):
|
||||
return
|
||||
self.next_step()
|
||||
if self.step() == "noop":
|
||||
# close the tray once everything is inventoried
|
||||
self.shuttle_id.release_vertical_lift_tray()
|
||||
# sorry not sorry
|
||||
return self._rainbow_man()
|
||||
|
||||
@@ -82,5 +82,9 @@ class VerticalLiftOperationPick(models.Model):
|
||||
"""Release the operation, go to the next"""
|
||||
super().button_release()
|
||||
if self.step() == "noop":
|
||||
# we don't need to release (close) the tray until we have reached
|
||||
# the last line: the release is implicit when a next line is
|
||||
# fetched
|
||||
self.shuttle_id.release_vertical_lift_tray()
|
||||
# sorry not sorry
|
||||
return self._rainbow_man()
|
||||
|
||||
@@ -172,6 +172,9 @@ class VerticalLiftOperationPut(models.Model):
|
||||
self.current_move_line_id.fetch_vertical_lift_tray_dest()
|
||||
|
||||
def button_release(self):
|
||||
# release (close) the tray each time, because for put-away, we
|
||||
# never know if the operator will scan another line or not
|
||||
self.shuttle_id.release_vertical_lift_tray()
|
||||
super().button_release()
|
||||
if self.count_move_lines_to_do_all() == 0:
|
||||
# sorry not sorry
|
||||
|
||||
@@ -178,6 +178,7 @@ class VerticalLiftShuttle(models.Model):
|
||||
}
|
||||
|
||||
def action_back_to_settings(self):
|
||||
self.release_vertical_lift_tray()
|
||||
action_xmlid = "stock_vertical_lift.vertical_lift_shuttle_action"
|
||||
action = self.env.ref(action_xmlid).read()[0]
|
||||
action["target"] = "main"
|
||||
@@ -195,16 +196,55 @@ class VerticalLiftShuttle(models.Model):
|
||||
# TODO: should the mode be changed on all the shuttles at the same time?
|
||||
def switch_pick(self):
|
||||
self.mode = "pick"
|
||||
self.release_vertical_lift_tray()
|
||||
return self.action_open_screen()
|
||||
|
||||
def switch_put(self):
|
||||
self.mode = "put"
|
||||
self.release_vertical_lift_tray()
|
||||
return self.action_open_screen()
|
||||
|
||||
def switch_inventory(self):
|
||||
self.mode = "inventory"
|
||||
self.release_vertical_lift_tray()
|
||||
return self.action_open_screen()
|
||||
|
||||
def _hardware_vertical_lift_release_tray_payload(self):
|
||||
"""Prepare "release" message to be sent 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.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 release (close)
|
||||
the tray.
|
||||
|
||||
Returns a message in bytes, that will be sent through
|
||||
``VerticalLiftShuttle._hardware_send_message()``.
|
||||
"""
|
||||
if self.hardware == "simulation":
|
||||
message = _("Releasing tray")
|
||||
return message.encode("utf-8")
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def release_vertical_lift_tray(self):
|
||||
"""Send instructions to the vertical lift hardware to close trays
|
||||
|
||||
The actual implementation of the method goes in the private method
|
||||
``_hardware_vertical_lift_release_tray()``.
|
||||
"""
|
||||
self.ensure_one()
|
||||
payload = self._hardware_vertical_lift_release_tray_payload()
|
||||
return self._hardware_send_message(payload)
|
||||
|
||||
def _send_notification_refresh(self, success):
|
||||
"""Send a refresh notification to the current opened screen
|
||||
|
||||
|
||||
@@ -15,6 +15,15 @@
|
||||
icon="fa-hand-paper-o"
|
||||
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('tray', 'cell'))]}"
|
||||
/>
|
||||
<button
|
||||
name="button_release_vertical_lift_tray"
|
||||
string="Release Shuttle Tray"
|
||||
type="object"
|
||||
groups="stock.group_stock_manager"
|
||||
class="oe_stat_button"
|
||||
icon="fa-hand-rock-o"
|
||||
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('tray', 'cell'))]}"
|
||||
/>
|
||||
</div>
|
||||
<field name="return_location" position="after">
|
||||
<field
|
||||
|
||||
@@ -66,32 +66,48 @@
|
||||
<field name="name">vertical.lift.shuttle.view.form</field>
|
||||
<field name="model">vertical.lift.shuttle</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Operations">
|
||||
<group name="main">
|
||||
<group name="left">
|
||||
<field name="name" />
|
||||
<field name="mode" />
|
||||
<field name="location_id" />
|
||||
<field name="hardware" />
|
||||
<form string="Shuttle">
|
||||
<header>
|
||||
<button
|
||||
name="release_vertical_lift_tray"
|
||||
string="Release tray"
|
||||
type="object"
|
||||
/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label class="oe_edit_only" for="name" />
|
||||
<h1>
|
||||
<field name="name" placeholder="Shuttle Name" />
|
||||
</h1>
|
||||
</div>
|
||||
<group name="main">
|
||||
<group name="left">
|
||||
<field name="mode" />
|
||||
<field name="location_id" />
|
||||
<field name="hardware" />
|
||||
</group>
|
||||
<group string="Network" name="network">
|
||||
<field name="server" />
|
||||
<field name="port" />
|
||||
<field name="use_tls" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Network" name="network">
|
||||
<field name="server" />
|
||||
<field name="port" />
|
||||
<field name="use_tls" />
|
||||
<group groups="base.group_no_one">
|
||||
<label for="command_ids" />
|
||||
<field name="command_ids">
|
||||
<tree>
|
||||
<field name="name" />
|
||||
<field name="command" />
|
||||
<field name="answer" />
|
||||
<field name="error" />
|
||||
<field name="create_date" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
<group groups="base.group_no_one">
|
||||
<label for="command_ids" />
|
||||
<field name="command_ids">
|
||||
<tree>
|
||||
<field name="name" />
|
||||
<field name="command" />
|
||||
<field name="answer" />
|
||||
<field name="error" />
|
||||
<field name="create_date" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -11,25 +11,13 @@ _logger = logging.getLogger(__name__)
|
||||
class StockLocation(models.Model):
|
||||
_inherit = "stock.location"
|
||||
|
||||
def _hardware_kardex_prepare_payload(self, cell_location=None):
|
||||
def _hardware_kardex_prepare_fetch_payload(self, cell_location=None):
|
||||
if self.level is False:
|
||||
raise exceptions.UserError(
|
||||
_("Shuttle tray %s has no level. " "Please fix the configuration")
|
||||
% self.display_name
|
||||
)
|
||||
message_template = (
|
||||
"{code}|{hostId}|{addr}|{carrier}|{carrierNext}|"
|
||||
"{x}|{y}|{boxType}|{Q}|{order}|{part}|{desc}|\r\n"
|
||||
)
|
||||
shuttle = self.vertical_lift_shuttle_id
|
||||
if shuttle.mode == "pick":
|
||||
code = "1"
|
||||
elif shuttle.mode == "put":
|
||||
code = "2"
|
||||
elif shuttle.mode == "inventory":
|
||||
code = "5"
|
||||
else:
|
||||
code = "61" # ping
|
||||
if cell_location:
|
||||
x, y = cell_location.tray_cell_center_position()
|
||||
if x == 0 and y == 0:
|
||||
@@ -45,7 +33,7 @@ class StockLocation(models.Model):
|
||||
else:
|
||||
x, y = "", ""
|
||||
subst = {
|
||||
"code": code,
|
||||
"code": shuttle._kardex_shuttle_code(),
|
||||
"hostId": self.env["ir.sequence"].next_by_code("vertical.lift.command"),
|
||||
# hard code the gate for now.
|
||||
# TODO proper handling of multiple gates for 1 lift.
|
||||
@@ -60,11 +48,10 @@ class StockLocation(models.Model):
|
||||
"part": "",
|
||||
"desc": "",
|
||||
}
|
||||
payload = message_template.format(**subst)
|
||||
return payload.encode("iso-8859-1", "replace")
|
||||
return shuttle._hardware_kardex_format_template(subst)
|
||||
|
||||
def _hardware_vertical_lift_tray_payload(self, cell_location=None):
|
||||
"""Prepare the message to be sent to the vertical lift hardware
|
||||
def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
|
||||
"""Prepare "fetch" message to be sent 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
|
||||
@@ -100,11 +87,12 @@ class StockLocation(models.Model):
|
||||
highlighting the cell using a laser pointer.
|
||||
"""
|
||||
if self.vertical_lift_shuttle_id.hardware == "kardex":
|
||||
payload = self._hardware_kardex_prepare_payload(cell_location=cell_location)
|
||||
_logger.debug("Sending to kardex: {}", payload)
|
||||
# TODO implement the communication with kardex
|
||||
payload = self._hardware_kardex_prepare_fetch_payload(
|
||||
cell_location=cell_location
|
||||
)
|
||||
_logger.debug("Sending to kardex (fetch): {}", payload)
|
||||
else:
|
||||
payload = super()._hardware_vertical_lift_tray_payload(
|
||||
payload = super()._hardware_vertical_lift_fetch_tray_payload(
|
||||
cell_location=cell_location
|
||||
)
|
||||
return payload
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
# 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__)
|
||||
|
||||
|
||||
JMIF_STATUS = {
|
||||
0: "success",
|
||||
101: "common error",
|
||||
@@ -29,6 +34,65 @@ class VerticalLiftShuttle(models.Model):
|
||||
values += [("kardex", "Kardex")]
|
||||
return values
|
||||
|
||||
_kardex_message_template = (
|
||||
"{code}|{hostId}|{addr}|{carrier}|{carrierNext}|"
|
||||
"{x}|{y}|{boxType}|{Q}|{order}|{part}|{desc}|\r\n"
|
||||
)
|
||||
|
||||
def _hardware_kardex_format_template(self, values):
|
||||
payload = self._kardex_message_template.format(**values)
|
||||
return payload.encode("iso-8859-1", "replace")
|
||||
|
||||
def _kardex_shuttle_code(self):
|
||||
mapping = {"pick": "1", "put": "2", "inventory": "5"}
|
||||
ping = "61"
|
||||
return mapping.get(self.mode, ping)
|
||||
|
||||
def _hardware_kardex_prepare_release_payload(self):
|
||||
subst = {
|
||||
"code": self._kardex_shuttle_code(),
|
||||
"hostId": self.env["ir.sequence"].next_by_code("vertical.lift.command"),
|
||||
# hard code the gate for now.
|
||||
"addr": self.name + "-1",
|
||||
"carrier": "0",
|
||||
"carrierNext": "0",
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"boxType": "",
|
||||
"Q": "",
|
||||
"order": "",
|
||||
"part": "",
|
||||
"desc": "",
|
||||
}
|
||||
return self._hardware_kardex_format_template(subst)
|
||||
|
||||
def _hardware_vertical_lift_release_tray_payload(self):
|
||||
"""Prepare "release" message to be sent 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 release (close)
|
||||
the tray.
|
||||
|
||||
Returns a message in bytes, that will be sent through
|
||||
``VerticalLiftShuttle._hardware_send_message()``.
|
||||
"""
|
||||
if self.hardware == "kardex":
|
||||
payload = self._hardware_kardex_prepare_release_payload()
|
||||
_logger.debug("Sending to kardex (release): {}", payload)
|
||||
else:
|
||||
payload = super()._hardware_vertical_lift_release_tray_payload()
|
||||
return payload
|
||||
|
||||
def _check_server_response(self, command):
|
||||
response = command.answer
|
||||
code, sep, remaining = response.partition("|")
|
||||
|
||||
Reference in New Issue
Block a user