Split the shuttle operations in different models/views

Namely, the pick/put/inventory operations are now split in
different models.

Pick and Put share a model and customize their behavior, which is pretty
similar. The inventory operation will have a different view and
different workflow.

This changes will ease a lot the customization of the different
workflows and views.
This commit is contained in:
Guewen Baconnier
2019-10-09 17:09:08 +02:00
committed by Hai Lang
parent 4e00019c7d
commit 4d3ce20810
16 changed files with 781 additions and 566 deletions

View File

@@ -26,6 +26,10 @@
'views/stock_location_views.xml',
'views/stock_move_line_views.xml',
'views/vertical_lift_shuttle_views.xml',
'views/vertical_lift_operation_base_views.xml',
'views/vertical_lift_operation_pick_views.xml',
'views/vertical_lift_operation_put_views.xml',
'views/vertical_lift_operation_inventory_views.xml',
'views/stock_vertical_lift_templates.xml',
'views/shuttle_screen_templates.xml',
'security/ir.model.access.csv',

View File

@@ -1,4 +1,8 @@
from . import vertical_lift_shuttle
from . import vertical_lift_operation_base
from . import vertical_lift_operation_pick
from . import vertical_lift_operation_put
from . import vertical_lift_operation_inventory
from . import stock_location
from . import stock_move
from . import stock_move_line

View File

@@ -5,19 +5,24 @@ from odoo import api, models
class StockMove(models.Model):
_inherit = 'stock.move'
_inherit = "stock.move"
@api.multi
def write(self, vals):
result = super().write(vals)
if 'state' in vals:
if "state" in vals:
# We cannot have fields to depends on to invalidate these computed
# fields on vertical.lift.shuttle. But we know that when the state
# of any move line changes, we can invalidate them as the count of
# assigned move lines may change (and we track this in stock.move,
# not stock.move.line, becaus the state of the lines is a related
# to this one).
self.env['vertical.lift.shuttle'].invalidate_cache(
['number_of_ops', 'number_of_ops_all']
# fields on vertical.lift.operation.*. But we know that when the
# state of any move line changes, we can invalidate them as the
# count of assigned move lines may change (and we track this in
# stock.move, not stock.move.line, because the state of the lines
# is a related to this one).
models = (
"vertical.lift.operation.pick",
"vertical.lift.operation.put",
)
for model in models:
self.env[model].invalidate_cache(
["number_of_ops", "number_of_ops_all"]
)
return result

View File

@@ -10,7 +10,14 @@ class StockQuant(models.Model):
def _update_available_quantity(self, *args, **kwargs):
result = super()._update_available_quantity(*args, **kwargs)
# We cannot have fields to depends on to invalidate this computed
# fields on vertical.lift.shuttle. But we know that when the quantity
# of quant changes, we can invalidate the field on the shuttles.
self.env['vertical.lift.shuttle'].invalidate_cache(['tray_qty'])
# fields on vertical.lift.operation.* models. But we know that when the
# quantity of quant changes, we can invalidate the field
models = (
"vertical.lift.operation.pick",
"vertical.lift.operation.put",
)
for model in models:
self.env[model].invalidate_cache(
["tray_qty"]
)
return result

View File

@@ -0,0 +1,264 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.addons.base_sparse_field.models.fields import Serialized
class VerticalLiftOperationBase(models.AbstractModel):
"""Base model for shuttle operations (pick, put, inventory)"""
_name = "vertical.lift.operation.base"
_inherit = "barcodes.barcode_events_mixin"
_description = "Vertical Lift Operation - Base"
name = fields.Char(related="shuttle_id.name", readonly=True)
shuttle_id = fields.Many2one(
comodel_name="vertical.lift.shuttle", required=True, readonly=True
)
location_id = fields.Many2one(
related="shuttle_id.location_id", readonly=True
)
mode = fields.Selection(related="shuttle_id.mode", readonly=True)
operation_descr = fields.Char(
string="Operation", default="...", readonly=True
)
_sql_constraints = [
(
"shuttle_id_unique",
"UNIQUE(shuttle_id)",
"One pick can be run at a time for a shuttle.",
)
]
def on_barcode_scanned(self, barcode):
self.ensure_one()
# to implement in sub-classes
def on_screen_open(self):
"""Called when the screen is open
To implement in sub-classes.
"""
def action_open_screen(self):
return self.shuttle_id.action_open_screen()
def action_menu(self):
return self.shuttle_id.action_menu()
def action_manual_barcode(self):
return self.shuttle_id.action_manual_barcode()
class VerticalLiftOperationTransfer(models.AbstractModel):
"""Base model for shuttle pick and put operations"""
_name = "vertical.lift.operation.transfer"
_inherit = "vertical.lift.operation.base"
_description = "Vertical Lift Operation - Transfer"
current_move_line_id = fields.Many2one(comodel_name="stock.move.line")
number_of_ops = fields.Integer(
compute="_compute_number_of_ops", string="Number of Operations"
)
number_of_ops_all = fields.Integer(
compute="_compute_number_of_ops_all",
string="Number of Operations in all shuttles",
)
tray_location_id = fields.Many2one(
comodel_name="stock.location",
compute="_compute_tray_data",
string="Tray Location",
)
tray_name = fields.Char(compute="_compute_tray_data", string="Tray Name")
tray_type_id = fields.Many2one(
comodel_name="stock.location.tray.type",
compute="_compute_tray_data",
string="Tray Type",
)
tray_type_code = fields.Char(
compute="_compute_tray_data", string="Tray Code"
)
tray_x = fields.Integer(string="X", compute="_compute_tray_data")
tray_y = fields.Integer(string="Y", compute="_compute_tray_data")
tray_matrix = Serialized(string="Cells", compute="_compute_tray_data")
tray_qty = fields.Float(
string="Stock Quantity", compute="_compute_tray_qty"
)
# current operation information
picking_id = fields.Many2one(
related="current_move_line_id.picking_id", readonly=True
)
picking_origin = fields.Char(
related="current_move_line_id.picking_id.origin", readonly=True
)
picking_partner_id = fields.Many2one(
related="current_move_line_id.picking_id.partner_id", readonly=True
)
product_id = fields.Many2one(
related="current_move_line_id.product_id", readonly=True
)
product_uom_id = fields.Many2one(
related="current_move_line_id.product_uom_id", readonly=True
)
product_uom_qty = fields.Float(
related="current_move_line_id.product_uom_qty", readonly=True
)
product_packagings = fields.Html(
string="Packaging", compute="_compute_product_packagings"
)
qty_done = fields.Float(
related="current_move_line_id.qty_done", readonly=True
)
lot_id = fields.Many2one(
related="current_move_line_id.lot_id", readonly=True
)
location_dest_id = fields.Many2one(
string="Destination",
related="current_move_line_id.location_dest_id",
readonly=True,
)
# TODO add a glue addon with product_expiry to add the field
def on_barcode_scanned(self, barcode):
self.ensure_one()
self.env.user.notify_info(
"Scanned barcode: {}. Not implemented.".format(barcode)
)
@api.depends("current_move_line_id.product_id.packaging_ids")
def _compute_product_packagings(self):
for record in self:
if not record.current_move_line_id:
continue
product = record.current_move_line_id.product_id
values = {
"packagings": [
{
"name": pkg.name,
"qty": pkg.qty,
"unit": product.uom_id.name,
}
for pkg in product.packaging_ids
]
}
content = self.env["ir.qweb"].render(
"stock_vertical_lift.packagings", values
)
record.product_packagings = content
@api.depends()
def _compute_number_of_ops(self):
for record in self:
record.number_of_ops = record.count_move_lines_to_do()
@api.depends()
def _compute_number_of_ops_all(self):
for record in self:
record.number_of_ops_all = record.count_move_lines_to_do_all()
@api.depends("tray_location_id", "current_move_line_id.product_id")
def _compute_tray_qty(self):
for record in self:
if not (record.tray_location_id and record.current_move_line_id):
continue
product = record.current_move_line_id.product_id
quants = self.env["stock.quant"].search(
[
("location_id", "=", record.tray_location_id.id),
("product_id", "=", product.id),
]
)
record.tray_qty = sum(quants.mapped("quantity"))
# depends of the quantity so we can't have all triggers
@api.depends("current_move_line_id")
def _compute_tray_data(self):
for record in self:
modes = {"pick": "location_id", "put": "location_dest_id"}
location = record.current_move_line_id[modes[record.mode]]
tray_type = location.location_id.tray_type_id
# this is the current cell
record.tray_location_id = location.id
# name of the tray where the cell is
record.tray_name = location.location_id.name
record.tray_type_id = tray_type.id
record.tray_type_code = tray_type.code
record.tray_x = location.posx
record.tray_y = location.posy
record.tray_matrix = location.tray_matrix
def _domain_move_lines_to_do(self):
# to implement in sub-classes
return [("id", "=", 0)]
def _domain_move_lines_to_do_all(self):
# to implement in sub-classes
return [("id", "=", 0)]
def count_move_lines_to_do(self):
"""Count move lines to process in current shuttles"""
self.ensure_one()
return self.env["stock.move.line"].search_count(
self._domain_move_lines_to_do()
)
def count_move_lines_to_do_all(self):
"""Count move lines to process in all shuttles"""
self.ensure_one()
return self.env["stock.move.line"].search_count(
self._domain_move_lines_to_do_all()
)
def on_screen_open(self):
"""Called when the screen is open"""
self.select_next_move_line()
def button_release(self):
"""Release the operation, go to the next"""
self.select_next_move_line()
if not self.current_move_line_id:
# sorry not sorry
return {
"effect": {
"fadeout": "slow",
"message": _("Congrats, you cleared the queue!"),
"img_url": "/web/static/src/img/smile.svg",
"type": "rainbow_man",
}
}
def process_current(self):
raise NotImplementedError
def button_save(self):
if not (self and self.current_move_line_id):
return
self.ensure_one()
self.process_current()
self.operation_descr = _("Release")
def fetch_tray(self):
return
def select_next_move_line(self):
self.ensure_one()
next_move_line = self.env["stock.move.line"].search(
self._domain_move_lines_to_do(), limit=1
)
self.current_move_line_id = next_move_line
# TODO use a state machine to define next steps and
# description?
descr = (
_("Scan New Destination Location")
if next_move_line
else _("No operations")
)
self.operation_descr = descr
if next_move_line:
self.fetch_tray()

View File

@@ -0,0 +1,10 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class VerticalLiftOperationInventory(models.Model):
_name = 'vertical.lift.operation.inventory'
_inherit = 'vertical.lift.operation.base'
_description = 'Vertical Lift Operation Inventory'

View File

@@ -0,0 +1,53 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, models
class VerticalLiftOperationPick(models.Model):
_name = "vertical.lift.operation.pick"
_inherit = "vertical.lift.operation.transfer"
_description = "Vertical Lift Operation Pick"
def on_barcode_scanned(self, barcode):
self.ensure_one()
location = self.env["stock.location"].search(
[("barcode", "=", barcode)]
)
if location:
self.current_move_line_id.location_dest_id = location
self.operation_descr = _("Save")
else:
self.env.user.notify_warning(
_("No location found for barcode {}").format(barcode)
)
def _domain_move_lines_to_do(self):
# TODO check domain
domain = [
("state", "=", "assigned"),
("location_id", "child_of", self.location_id.id),
]
return domain
def _domain_move_lines_to_do_all(self):
shuttle_locations = self.env["stock.location"].search(
[("vertical_lift_kind", "=", "view")]
)
# TODO check domain
domain = [
("state", "=", "assigned"),
("location_id", "child_of", shuttle_locations.ids),
]
return domain
def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_source()
def process_current(self):
# test code, TODO the smart one
# (scan of barcode increments qty, save calls action_done?)
line = self.current_move_line_id
if line.state != "done":
line.qty_done = line.product_qty
line.move_id._action_done()

View File

@@ -0,0 +1,35 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, exceptions, models
class VerticalLiftOperationPut(models.Model):
_name = "vertical.lift.operation.put"
_inherit = "vertical.lift.operation.transfer"
_description = "Vertical Lift Operation Put"
def _domain_move_lines_to_do(self):
# TODO check domain
domain = [
("state", "=", "assigned"),
("location_dest_id", "child_of", self.location_id.id),
]
return domain
def _domain_move_lines_to_do_all(self):
shuttle_locations = self.env["stock.location"].search(
[("vertical_lift_kind", "=", "view")]
)
domain = [
# TODO check state
("state", "=", "assigned"),
("location_dest_id", "child_of", shuttle_locations.ids),
]
return domain
def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_dest()
def process_current(self):
raise exceptions.UserError(_("Put workflow not implemented"))

View File

@@ -1,384 +1,141 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, exceptions, fields, models
from odoo.addons.base_sparse_field.models.fields import Serialized
from odoo import _, api, fields, models
class VerticalLiftShuttle(models.Model):
_name = 'vertical.lift.shuttle'
_inherit = 'barcodes.barcode_events_mixin'
_description = 'Vertical Lift Shuttle'
_name = "vertical.lift.shuttle"
_inherit = "barcodes.barcode_events_mixin"
_description = "Vertical Lift Shuttle"
name = fields.Char()
mode = fields.Selection(
[('pick', 'Pick'), ('put', 'Put'), ('inventory', 'Inventory')],
default='pick',
[("pick", "Pick"), ("put", "Put"), ("inventory", "Inventory")],
default="pick",
required=True,
)
location_id = fields.Many2one(
comodel_name='stock.location',
comodel_name="stock.location",
required=True,
domain="[('vertical_lift_kind', '=', 'shuttle')]",
ondelete='restrict',
ondelete="restrict",
help="The Shuttle source location for Pick operations "
"and destination location for Put operations.",
)
hardware = fields.Selection(
selection='_selection_hardware', default='simulation', required=True
)
current_move_line_id = fields.Many2one(comodel_name='stock.move.line')
number_of_ops = fields.Integer(
compute='_compute_number_of_ops', string='Number of Operations'
)
number_of_ops_all = fields.Integer(
compute='_compute_number_of_ops_all',
string='Number of Operations in all shuttles',
)
operation_descr = fields.Char(
string="Operation",
default="Scan New Destination Location",
readonly=True,
)
# tray information (will come from stock.location or a new tray model)
tray_location_id = fields.Many2one(
comodel_name='stock.location',
compute='_compute_tray_matrix',
string='Tray Location',
)
tray_name = fields.Char(compute='_compute_tray_matrix', string='Tray Name')
tray_type_id = fields.Many2one(
comodel_name='stock.location.tray.type',
compute='_compute_tray_matrix',
string='Tray Type',
)
tray_type_code = fields.Char(
compute='_compute_tray_matrix', string='Tray Code'
)
tray_x = fields.Integer(string='X', compute='_compute_tray_matrix')
tray_y = fields.Integer(string='Y', compute='_compute_tray_matrix')
tray_matrix = Serialized(string='Cells', compute='_compute_tray_matrix')
tray_qty = fields.Float(
string='Stock Quantity', compute='_compute_tray_qty'
)
# current operation information
picking_id = fields.Many2one(
related='current_move_line_id.picking_id', readonly=True
)
picking_origin = fields.Char(
related='current_move_line_id.picking_id.origin', readonly=True
)
picking_partner_id = fields.Many2one(
related='current_move_line_id.picking_id.partner_id', readonly=True
)
product_id = fields.Many2one(
related='current_move_line_id.product_id', readonly=True
)
product_uom_id = fields.Many2one(
related='current_move_line_id.product_uom_id', readonly=True
)
product_uom_qty = fields.Float(
related='current_move_line_id.product_uom_qty', readonly=True
)
product_packagings = fields.Html(
string='Packaging', compute='_compute_product_packagings'
)
qty_done = fields.Float(
related='current_move_line_id.qty_done', readonly=True
)
lot_id = fields.Many2one(
related='current_move_line_id.lot_id', readonly=True
)
location_dest_id = fields.Many2one(
string="Destination",
related='current_move_line_id.location_dest_id',
readonly=True,
)
# TODO add a glue addon with product_expiry to add the field
_barcode_scanned = fields.Char(
"Barcode Scanned",
help="Value of the last barcode scanned.",
store=False,
selection="_selection_hardware", default="simulation", required=True
)
_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
self.env.user.notify_info('Scanned barcode: {}'.format(barcode))
method = 'on_barcode_scanned_{}'.format(self.mode)
getattr(self, method)(barcode)
def on_barcode_scanned_pick(self, barcode):
location = self.env['stock.location'].search(
[('barcode', '=', barcode)]
(
"location_id_unique",
"UNIQUE(location_id)",
"You cannot have two shuttles using the same location.",
)
if location:
self.current_move_line_id.location_dest_id = location
self.operation_descr = _('Save')
else:
self.env.user.notify_warning(
_('No location found for barcode {}').format(barcode)
)
def on_barcode_scanned_put(self, barcode):
pass
def on_barcode_scanned_inventory(self, barcode):
pass
]
@api.model
def _selection_hardware(self):
return [('simulation', 'Simulation')]
return [("simulation", "Simulation")]
@api.depends('current_move_line_id.product_id.packaging_ids')
def _compute_product_packagings(self):
for record in self:
if not record.current_move_line_id:
continue
product = record.current_move_line_id.product_id
values = {
'packagings': [
{
'name': pkg.name,
'qty': pkg.qty,
'unit': product.uom_id.name,
}
for pkg in product.packaging_ids
]
}
content = self.env['ir.qweb'].render(
'stock_vertical_lift.packagings', values
)
record.product_packagings = content
@api.depends()
def _compute_number_of_ops(self):
for record in self:
record.number_of_ops = record.count_move_lines_to_do()
@api.depends()
def _compute_number_of_ops_all(self):
for record in self:
record.number_of_ops_all = record.count_move_lines_to_do_all()
@api.depends('tray_location_id', 'current_move_line_id.product_id')
def _compute_tray_qty(self):
for record in self:
if not (record.tray_location_id and record.current_move_line_id):
continue
product = record.current_move_line_id.product_id
quants = self.env['stock.quant'].search(
[
('location_id', '=', record.tray_location_id.id),
('product_id', '=', product.id),
]
)
record.tray_qty = sum(quants.mapped('quantity'))
@api.depends()
def _compute_tray_matrix(self):
for record in self:
modes = {
'pick': 'location_id',
'put': 'location_dest_id',
# TODO what to do for inventory?
'inventory': 'location_id',
}
location = record.current_move_line_id[modes[record.mode]]
tray_type = location.location_id.tray_type_id
selected = []
cells = []
if location:
selected = location._tray_cell_coords()
cells = location._tray_cell_matrix()
# this is the current cell
record.tray_location_id = location.id
# name of the tray where the cell is
record.tray_name = location.location_id.name
record.tray_type_id = tray_type.id
record.tray_type_code = tray_type.code
record.tray_x = location.posx
record.tray_y = location.posy
record.tray_matrix = {
# x, y: position of the selected cell
'selected': selected,
# 0 is empty, 1 is not
'cells': cells,
}
def _domain_move_lines_to_do(self):
domain = [
# TODO check state
('state', '=', 'assigned')
]
domain_extensions = {
'pick': [('location_id', 'child_of', self.location_id.id)],
# TODO ensure that we cannot have the same ml in 2 shuttles (cannot
# happen with 'pick' as they are in the shuttle's location)
'put': [('location_dest_id', 'child_of', self.location_id.id)],
# TODO
'inventory': [('id', '=', 0)],
@property
def _model_for_mode(self):
return {
"pick": "vertical.lift.operation.pick",
"put": "vertical.lift.operation.put",
"inventory": "vertical.lift.operation.inventory",
}
return domain + domain_extensions[self.mode]
def _domain_move_lines_to_do_all(self):
domain = [
# TODO check state
('state', '=', 'assigned')
]
# TODO search only in the view being a parent of shuttle's location
shuttle_locations = self.env['stock.location'].search(
[('vertical_lift_kind', '=', 'view')]
)
domain_extensions = {
'pick': [('location_id', 'child_of', shuttle_locations.ids)],
'put': [('location_dest_id', 'child_of', shuttle_locations.ids)],
# TODO
'inventory': [('id', '=', 0)],
@property
def _screen_view_for_mode(self):
return {
"pick": (
"stock_vertical_lift."
"vertical_lift_operation_pick_screen_view"
),
"put": (
"stock_vertical_lift."
"vertical_lift_operation_put_screen_view"
),
"inventory": (
"stock_vertical_lift."
"vertical_lift_operation_inventory_screen_view"
),
}
return domain + domain_extensions[self.mode]
def count_move_lines_to_do(self):
self.ensure_one()
return self.env['stock.move.line'].search_count(
self._domain_move_lines_to_do()
)
def count_move_lines_to_do_all(self):
self.ensure_one()
return self.env['stock.move.line'].search_count(
self._domain_move_lines_to_do_all()
)
def button_release(self):
self.select_next_move_line()
if not self.current_move_line_id:
# sorry not sorry
return {
'effect': {
'fadeout': 'slow',
'message': _('Congrats, you cleared the queue!'),
'img_url': '/web/static/src/img/smile.svg',
'type': 'rainbow_man',
}
}
def process_current_pick(self):
# test code, TODO the smart one
# (scan of barcode increments qty, save calls action_done?)
line = self.current_move_line_id
if line.state != 'done':
line.qty_done = line.product_qty
line.move_id._action_done()
def process_current_put(self):
raise exceptions.UserError(_('Put workflow not implemented'))
def process_current_inventory(self):
raise exceptions.UserError(_('Inventory workflow not implemented'))
def button_save(self):
if not (self and self.current_move_line_id):
return
self.ensure_one()
method = 'process_current_{}'.format(self.mode)
getattr(self, method)()
self.operation_descr = _('Release')
def select_next_move_line(self):
self.ensure_one()
next_move_line = self.env['stock.move.line'].search(
self._domain_move_lines_to_do(), limit=1
)
self.current_move_line_id = next_move_line
# TODO use a state machine to define next steps and
# description?
descr = (
_('Scan New Destination Location')
if next_move_line
else _('No operations')
)
self.operation_descr = descr
if next_move_line:
# TODO different method (source vs dest) on pick/put scenario
next_move_line.fetch_vertical_lift_tray_source()
def _operation_for_mode(self):
model = self._model_for_mode[self.mode]
record = self.env[model].search([("shuttle_id", "=", self.id)])
if not record:
record = self.env[model].create({"shuttle_id": self.id})
return record
def action_open_screen(self):
self.select_next_move_line()
self.ensure_one()
screen_xmlid = (
'stock_vertical_lift.vertical_lift_shuttle_view_form_screen'
)
assert self.mode in ("pick", "put", "inventory")
screen_xmlid = self._screen_view_for_mode[self.mode]
operation = self._operation_for_mode()
operation.on_screen_open()
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'views': [[self.env.ref(screen_xmlid).id, 'form']],
'res_id': self.id,
'target': 'fullscreen',
'flags': {
'headless': True,
'form_view_initial_mode': 'edit',
'no_breadcrumbs': True,
"type": "ir.actions.act_window",
"res_model": operation._name,
"views": [[self.env.ref(screen_xmlid).id, "form"]],
"res_id": operation.id,
"target": "fullscreen",
"flags": {
"headless": True,
"form_view_initial_mode": "edit",
"no_breadcrumbs": True,
},
}
def action_menu(self):
menu_xmlid = 'stock_vertical_lift.vertical_lift_shuttle_form_menu'
menu_xmlid = "stock_vertical_lift.vertical_lift_shuttle_form_menu"
return {
'type': 'ir.actions.act_window',
'res_model': 'vertical.lift.shuttle',
'views': [[self.env.ref(menu_xmlid).id, 'form']],
'name': _('Menu'),
'target': 'new',
'res_id': self.id,
"type": "ir.actions.act_window",
"res_model": "vertical.lift.shuttle",
"views": [[self.env.ref(menu_xmlid).id, "form"]],
"name": _("Menu"),
"target": "new",
"res_id": self.id,
}
def action_manual_barcode(self):
return {
'type': 'ir.actions.act_window',
'res_model': 'vertical.lift.shuttle.manual.barcode',
'view_mode': 'form',
'name': _('Barcode'),
'target': 'new',
"type": "ir.actions.act_window",
"res_model": "vertical.lift.shuttle.manual.barcode",
"view_mode": "form",
"name": _("Barcode"),
"target": "new",
}
# TODO: should the mode be changed on all the shuttles at the same time?
def switch_pick(self):
self.mode = 'pick'
self.select_next_move_line()
self.mode = "pick"
return self.action_open_screen()
def switch_put(self):
self.mode = 'put'
self.select_next_move_line()
self.mode = "put"
return self.action_open_screen()
def switch_inventory(self):
self.mode = 'inventory'
self.select_next_move_line()
self.mode = "inventory"
return self.action_open_screen()
class VerticalLiftShuttleManualBarcode(models.TransientModel):
_name = 'vertical.lift.shuttle.manual.barcode'
_description = 'Action to input a barcode'
_name = "vertical.lift.shuttle.manual.barcode"
_description = "Action to input a barcode"
barcode = fields.Char(string="Barcode")
@api.multi
def button_save(self):
shuttle_id = self.env.context.get('active_id')
shuttle = self.env['vertical.lift.shuttle'].browse(shuttle_id).exists()
if not shuttle:
active_id = self.env.context.get("active_id")
model = self.env.context.get("active_model")
record = self.env[model].browse(active_id).exists()
if not record:
return
if self.barcode:
shuttle.on_barcode_scanned(self.barcode)
record.on_barcode_scanned(self.barcode)

View File

@@ -1,3 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_vertical_lift_shuttle_stock_user,access_vertical_lift_shuttle stock user,model_vertical_lift_shuttle,stock.group_stock_user,1,0,0,0
access_vertical_lift_shuttle_manager,access_vertical_lift_shuttle stock manager,model_vertical_lift_shuttle,stock.group_stock_manager,1,1,1,1
access_vertical_lift_operation_pick_stock_user,access_vertical_lift_operation_pick stock user,model_vertical_lift_operation_pick,stock.group_stock_user,1,1,1,1
access_vertical_lift_operation_put_stock_user,access_vertical_lift_operation_put stock user,model_vertical_lift_operation_put,stock.group_stock_user,1,1,1,1
access_vertical_lift_operation_inventory_stock_user,access_vertical_lift_operation_inventory stock user,model_vertical_lift_operation_inventory,stock.group_stock_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_vertical_lift_shuttle_stock_user access_vertical_lift_shuttle stock user model_vertical_lift_shuttle stock.group_stock_user 1 0 0 0
3 access_vertical_lift_shuttle_manager access_vertical_lift_shuttle stock manager model_vertical_lift_shuttle stock.group_stock_manager 1 1 1 1
4 access_vertical_lift_operation_pick_stock_user access_vertical_lift_operation_pick stock user model_vertical_lift_operation_pick stock.group_stock_user 1 1 1 1
5 access_vertical_lift_operation_put_stock_user access_vertical_lift_operation_put stock user model_vertical_lift_operation_put stock.group_stock_user 1 1 1 1
6 access_vertical_lift_operation_inventory_stock_user access_vertical_lift_operation_inventory stock user model_vertical_lift_operation_inventory stock.group_stock_user 1 1 1 1

View File

@@ -13,7 +13,7 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
def setUpClass(cls):
super().setUpClass()
cls.picking_out = cls.env.ref(
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
)
# we have a move line to pick created by demo picking
# stock_picking_out_demo_vertical_lift_1
@@ -21,114 +21,112 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
def test_switch_pick(self):
self.shuttle.switch_pick()
self.assertEqual(self.shuttle.mode, 'pick')
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
self.assertEqual(self.shuttle.mode, "pick")
self.assertEqual(
self.shuttle._operation_for_mode().current_move_line_id,
self.out_move_line,
)
def test_switch_put(self):
self.shuttle.switch_put()
self.assertEqual(self.shuttle.mode, 'put')
self.assertEqual(self.shuttle.mode, "put")
# TODO check that we have an incoming move when switching
self.assertEqual(
self.shuttle.current_move_line_id,
self.env['stock.move.line'].browse(),
self.shuttle._operation_for_mode().current_move_line_id,
self.env["stock.move.line"].browse(),
)
def test_switch_inventory(self):
self.shuttle.switch_inventory()
self.assertEqual(self.shuttle.mode, 'inventory')
# TODO check that we have what we should (what?)
self.assertEqual(
self.shuttle.current_move_line_id,
self.env['stock.move.line'].browse(),
)
self.assertEqual(self.shuttle.mode, "inventory")
def test_pick_action_open_screen(self):
self.shuttle.switch_pick()
action = self.shuttle.action_open_screen()
self.assertTrue(self.shuttle.current_move_line_id)
self.assertEqual(action['type'], 'ir.actions.act_window')
self.assertEqual(action['res_model'], 'vertical.lift.shuttle')
self.assertEqual(action['res_id'], self.shuttle.id)
operation = self.shuttle._operation_for_mode()
self.assertTrue(operation.current_move_line_id)
self.assertEqual(action["type"], "ir.actions.act_window")
self.assertEqual(action["res_model"], "vertical.lift.operation.pick")
self.assertEqual(action["res_id"], operation.id)
def test_pick_select_next_move_line(self):
self.shuttle.switch_pick()
self.shuttle.select_next_move_line()
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
self.shuttle.mode = "pick"
operation = self.shuttle._operation_for_mode()
operation.select_next_move_line()
self.assertEqual(operation.current_move_line_id, self.out_move_line)
self.assertEqual(
self.shuttle.operation_descr,
_('Scan New Destination Location')
operation.operation_descr, _("Scan New Destination Location")
)
def test_pick_save(self):
self.shuttle.switch_pick()
self.shuttle.current_move_line_id = self.out_move_line
self.shuttle.button_save()
self.assertEqual(
self.shuttle.current_move_line_id.state,
'done'
)
self.assertEqual(self.shuttle.operation_descr, _('Release'))
operation = self.shuttle._operation_for_mode()
operation.current_move_line_id = self.out_move_line
operation.button_save()
self.assertEqual(operation.current_move_line_id.state, "done")
self.assertEqual(operation.operation_descr, _("Release"))
def test_pick_related_fields(self):
self.shuttle.switch_pick()
ml = self.shuttle.current_move_line_id = self.out_move_line
operation = self.shuttle._operation_for_mode()
ml = operation.current_move_line_id = self.out_move_line
# Trays related fields
# For pick, this is the source location, which is the cell where the
# product is.
self.assertEqual(self.shuttle.tray_location_id, ml.location_id)
self.assertEqual(operation.tray_location_id, ml.location_id)
self.assertEqual(
self.shuttle.tray_name,
operation.tray_name,
# parent = tray
ml.location_id.location_id.name,
)
self.assertEqual(
self.shuttle.tray_type_id,
operation.tray_type_id,
# the tray type is on the parent of the cell (on the tray)
ml.location_id.location_id.tray_type_id,
)
self.assertEqual(
self.shuttle.tray_type_code,
operation.tray_type_code,
ml.location_id.location_id.tray_type_id.code,
)
self.assertEqual(self.shuttle.tray_x, ml.location_id.posx)
self.assertEqual(self.shuttle.tray_y, ml.location_id.posy)
self.assertEqual(operation.tray_x, ml.location_id.posx)
self.assertEqual(operation.tray_y, ml.location_id.posy)
# Move line related fields
self.assertEqual(self.shuttle.picking_id, ml.picking_id)
self.assertEqual(self.shuttle.picking_origin, ml.picking_id.origin)
self.assertEqual(operation.picking_id, ml.picking_id)
self.assertEqual(operation.picking_origin, ml.picking_id.origin)
self.assertEqual(
self.shuttle.picking_partner_id, ml.picking_id.partner_id
operation.picking_partner_id, ml.picking_id.partner_id
)
self.assertEqual(self.shuttle.product_id, ml.product_id)
self.assertEqual(self.shuttle.product_uom_id, ml.product_uom_id)
self.assertEqual(self.shuttle.product_uom_qty, ml.product_uom_qty)
self.assertEqual(self.shuttle.qty_done, ml.qty_done)
self.assertEqual(self.shuttle.lot_id, ml.lot_id)
self.assertEqual(operation.product_id, ml.product_id)
self.assertEqual(operation.product_uom_id, ml.product_uom_id)
self.assertEqual(operation.product_uom_qty, ml.product_uom_qty)
self.assertEqual(operation.qty_done, ml.qty_done)
self.assertEqual(operation.lot_id, ml.lot_id)
def test_pick_count_move_lines(self):
product1 = self.env.ref('stock_vertical_lift.product_running_socks')
product2 = self.env.ref('stock_vertical_lift.product_recovery_socks')
product1 = self.env.ref("stock_vertical_lift.product_running_socks")
product2 = self.env.ref("stock_vertical_lift.product_recovery_socks")
# cancel the picking from demo data to start from a clean state
self.env.ref(
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
).action_cancel()
# ensure that we have stock in some cells, we'll put product1
# in the first Shuttle and product2 in the second
cell1 = self.env.ref(
'stock_vertical_lift.'
'stock_location_vertical_lift_demo_tray_1a_x3y2'
"stock_vertical_lift."
"stock_location_vertical_lift_demo_tray_1a_x3y2"
)
self._update_quantity_in_cell(cell1, product1, 50)
cell2 = self.env.ref(
'stock_vertical_lift.'
'stock_location_vertical_lift_demo_tray_2a_x1y1'
"stock_vertical_lift."
"stock_location_vertical_lift_demo_tray_2a_x1y1"
)
self._update_quantity_in_cell(cell2, product2, 50)
# create pickings (we already have an existing one from demo data)
pickings = self.env['stock.picking'].browse()
pickings = self.env["stock.picking"].browse()
pickings |= self._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product1, 1)
@@ -144,41 +142,43 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
pickings.action_assign()
shuttle1 = self.shuttle
operation1 = shuttle1._operation_for_mode()
shuttle2 = self.env.ref(
'stock_vertical_lift.stock_vertical_lift_demo_shuttle_2'
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_2"
)
operation2 = shuttle2._operation_for_mode()
self.assertEqual(shuttle1.number_of_ops, 4)
self.assertEqual(shuttle2.number_of_ops, 2)
self.assertEqual(shuttle1.number_of_ops_all, 6)
self.assertEqual(shuttle2.number_of_ops_all, 6)
self.assertEqual(operation1.number_of_ops, 4)
self.assertEqual(operation2.number_of_ops, 2)
self.assertEqual(operation1.number_of_ops_all, 6)
self.assertEqual(operation2.number_of_ops_all, 6)
# Process a line, should change the numbers.
shuttle1.select_next_move_line()
shuttle1.process_current_pick()
self.assertEqual(shuttle1.number_of_ops, 3)
self.assertEqual(shuttle2.number_of_ops, 2)
self.assertEqual(shuttle1.number_of_ops_all, 5)
self.assertEqual(shuttle2.number_of_ops_all, 5)
operation1.select_next_move_line()
operation1.process_current()
self.assertEqual(operation1.number_of_ops, 3)
self.assertEqual(operation2.number_of_ops, 2)
self.assertEqual(operation1.number_of_ops_all, 5)
self.assertEqual(operation2.number_of_ops_all, 5)
# add stock and make the last one assigned to check the number is
# updated
self._update_quantity_in_cell(cell2, product2, 10)
unassigned.action_assign()
self.assertEqual(shuttle1.number_of_ops, 3)
self.assertEqual(shuttle2.number_of_ops, 3)
self.assertEqual(shuttle1.number_of_ops_all, 6)
self.assertEqual(shuttle2.number_of_ops_all, 6)
self.assertEqual(operation1.number_of_ops, 3)
self.assertEqual(operation2.number_of_ops, 3)
self.assertEqual(operation1.number_of_ops_all, 6)
self.assertEqual(operation2.number_of_ops_all, 6)
@unittest.skip('Not implemented')
@unittest.skip("Not implemented")
def test_put_count_move_lines(self):
pass
@unittest.skip('Not implemented')
@unittest.skip("Not implemented")
def test_inventory_count_move_lines(self):
pass
@unittest.skip('Not implemented')
@unittest.skip("Not implemented")
def test_on_barcode_scanned(self):
# test to implement when the code is implemented
pass
@@ -188,48 +188,52 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
self.out_move_line.qty_done = self.out_move_line.product_qty
self.out_move_line.move_id._action_done()
# release, no further operation in queue
result = self.shuttle.button_release()
self.assertFalse(self.shuttle.current_move_line_id)
self.assertEqual(self.shuttle.operation_descr, _('No operations'))
operation = self.shuttle._operation_for_mode()
result = operation.button_release()
self.assertFalse(operation.current_move_line_id)
self.assertEqual(operation.operation_descr, _("No operations"))
expected_result = {
'effect': {
'fadeout': 'slow',
'message': _('Congrats, you cleared the queue!'),
'img_url': '/web/static/src/img/smile.svg',
'type': 'rainbow_man',
"effect": {
"fadeout": "slow",
"message": _("Congrats, you cleared the queue!"),
"img_url": "/web/static/src/img/smile.svg",
"type": "rainbow_man",
}
}
self.assertEqual(result, expected_result)
def test_process_current_pick(self):
self.shuttle.switch_pick()
self.shuttle.current_move_line_id = self.out_move_line
operation = self.shuttle._operation_for_mode()
operation.current_move_line_id = self.out_move_line
qty_to_process = self.out_move_line.product_qty
self.shuttle.process_current_pick()
self.assertEqual(self.out_move_line.state, 'done')
operation.process_current()
self.assertEqual(self.out_move_line.state, "done")
self.assertEqual(self.out_move_line.qty_done, qty_to_process)
def test_process_current_put(self):
# test to implement when the code is implemented
self.shuttle.switch_put()
operation = self.shuttle._operation_for_mode()
with self.assertRaises(exceptions.UserError):
self.shuttle.process_current_put()
operation.process_current()
def test_process_current_inventory(self):
# test to implement when the code is implemented
with self.assertRaises(exceptions.UserError):
self.shuttle.process_current_inventory()
self.shuttle.switch_inventory()
def test_matrix(self):
self.shuttle.switch_pick()
self.shuttle.current_move_line_id = self.out_move_line
operation = self.shuttle._operation_for_mode()
operation.current_move_line_id = self.out_move_line
location = self.out_move_line.location_id
# offset by -1 because the fields are for humans
expected_x = location.posx - 1
expected_y = location.posy - 1
self.assertEqual(
self.shuttle.tray_matrix,
operation.tray_matrix,
{
'selected': [expected_x, expected_y],
"selected": [expected_x, expected_y],
# fmt: off
'cells': [
[0, 0, 0, 0, 0, 0, 0, 0],
@@ -241,12 +245,13 @@ class TestVerticalLiftTrayType(VerticalLiftCase):
def test_tray_qty(self):
cell = self.env.ref(
'stock_vertical_lift.'
'stock_location_vertical_lift_demo_tray_1a_x3y2'
"stock_vertical_lift."
"stock_location_vertical_lift_demo_tray_1a_x3y2"
)
self.out_move_line.location_id = cell
self.shuttle.current_move_line_id = self.out_move_line
operation = self.shuttle._operation_for_mode()
operation.current_move_line_id = self.out_move_line
self._update_quantity_in_cell(cell, self.out_move_line.product_id, 50)
self.assertEqual(self.shuttle.tray_qty, 50)
self.assertEqual(operation.tray_qty, 50)
self._update_quantity_in_cell(cell, self.out_move_line.product_id, -20)
self.assertEqual(self.shuttle.tray_qty, 30)
self.assertEqual(operation.tray_qty, 30)

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vertical_lift_operation_base_screen_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.base.screen.view</field>
<field name="model">vertical.lift.operation.base</field>
<field name="arch" type="xml">
<form string="Operations" delete="0" create="0" class="o_vlift_shuttle">
<div class="o_shuttle_header">
<div class="o_shuttle_header_left o_shuttle_header_content">
<field name="name" readonly="1"/>
</div>
<div class="o_shuttle_header_center o_shuttle_header_content">
<label for="mode"/>
<field name="mode" readonly="1"/>
</div>
<div class="o_shuttle_header_right o_shuttle_header_content">
</div>
</div>
<div class="o_shuttle_actions">
<div class="o_shuttle_content">
<button name="action_menu" type="object"
class="btn-secondary o_shuttle_icon_btn"
string="" icon="fa-bars"
aria-label="Dropdown menu" title="Dropdown menu"/>
<button name="action_manual_barcode" type="object"
class="btn-secondary o_shuttle_icon_btn"
string="" icon="fa-terminal"
aria-label="Barcode Input" title="Barcode Input"/>
</div>
<div class="o_shuttle_content o_shuttle_content_right">
<div>
<!-- will react on barcode 'O-BTN.save' -->
<button name="button_save"
type="object"
string="Save"
icon="fa-check"
class="btn-primary"
barcode_trigger="save"/>
<!-- will react on barcode 'O-BTN.release -->
<button name="button_release"
type="object"
string="Release"
class="btn-primary"
barcode_trigger="release"/>
</div>
</div>
</div>
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
<div class="container">
<field name="operation_descr"/>
</div>
</div>
<div class="o_shuttle_data">
</div>
<field name="_barcode_scanned" widget="barcode_handler"/>
</form>
</field>
</record>
<record id="vertical_lift_operation_transfer_screen_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.transfer.screen.view</field>
<field name="model">vertical.lift.operation.transfer</field>
<field name="inherit_id" ref="vertical_lift_operation_base_screen_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_shuttle_header_right')]" position="inside" >
<label for="number_of_ops"/>
<field name="number_of_ops" readonly="1"/> /
<field name="number_of_ops_all" readonly="1"/>
</xpath>
<xpath expr="//div[hasclass('o_shuttle_data')]" position="attributes" >
<attribute name="attrs" >{'invisible': [('current_move_line_id', '=', False)]}</attribute>
</xpath>
<xpath expr="//div[hasclass('o_shuttle_data')]" position="inside" >
<!-- on the left of the screen -->
<div class="o_shuttle_data_content o_shuttle_move">
<div>
<group>
<field name="current_move_line_id" invisible="1"/>
<label for="picking_id"/>
<div>
<div>
<field name="picking_id" options="{'no_open': True}" class="mr8"/>
<span>/</span>
<field name="picking_origin" class="oe_inline ml8"/>
</div>
</div>
<label for="picking_partner_id"/>
<div>
<field name="picking_partner_id" options="{'no_open': True}"/>
</div>
<!-- TODO change for pick and put? -->
<label for="location_dest_id"/>
<div>
<field name="location_dest_id"
class="bg-primary o_shuttle_highlight"
options="{'no_open': True}"/>
</div>
<label for="product_id"/>
<div colspan="2" class="oe_title">
<h1>
<field name="product_id" options="{'no_open': True}"/>
</h1>
</div>
<div colspan="2">
<field name="product_packagings"/>
</div>
<field name="lot_id"/>
<label for="product_uom_qty" string="Quantity" class="ml32"/>
<div colspan="2" class="ml32">
<h1 class="bg-primary o_shuttle_highlight">
<field name="product_uom_qty" class="oe_inline text-white"/>
<field name="product_uom_id" options="{'no_open': True}" class="oe_inline ml8 text-white"/>
</h1>
</div>
</group>
</div>
</div>
<!-- on the right of the screen -->
<div class="o_shuttle_data_content o_shuttle_tray">
<group col="1">
<field name="tray_name"/>
<field name="tray_type_code"/>
<field name="tray_y"/>
<field name="tray_x"/>
<label for="tray_qty"/>
<div colspan="2" class="oe_title">
<h1>
<field name="tray_qty"/>
</h1>
</div>
</group>
<group>
<div>
<field name="tray_matrix"
widget="location_tray_matrix"
/>
</div>
</group>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vertical_lift_operation_inventory_screen_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.inventory.screen.view</field>
<field name="model">vertical.lift.operation.inventory</field>
<field name="inherit_id" ref="vertical_lift_operation_base_screen_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Inventory Screen</attribute>
</form>
<xpath expr="//div[hasclass('o_shuttle_data')]" position="inside" >
<span>Not implemented</span>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vertical_lift_operation_pick_screen_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.pick.screen.view</field>
<field name="model">vertical.lift.operation.pick</field>
<field name="inherit_id" ref="vertical_lift_operation_transfer_screen_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Pick Screen</attribute>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vertical_lift_operation_put_screen_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.put.screen.view</field>
<field name="model">vertical.lift.operation.put</field>
<field name="inherit_id" ref="vertical_lift_operation_transfer_screen_view"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Put Screen</attribute>
</form>
</field>
</record>
</odoo>

View File

@@ -1,133 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="vertical_lift_shuttle_view_form_screen" model="ir.ui.view">
<field name="name">vertical.lift.shuttle.view.form.screen</field>
<field name="model">vertical.lift.shuttle</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<form string="Operations" delete="0" create="0" class="o_vlift_shuttle">
<!-- <field name="product_id" invisible="1"/> -->
<div class="o_shuttle_header">
<div class="o_shuttle_header_left o_shuttle_header_content">
<field name="name" readonly="1"/>
</div>
<div class="o_shuttle_header_center o_shuttle_header_content">
<label for="mode"/>
<field name="mode" readonly="1"/>
</div>
<div class="o_shuttle_header_right o_shuttle_header_content">
<label for="number_of_ops"/>
<field name="number_of_ops" readonly="1"/> /
<field name="number_of_ops_all" readonly="1"/>
</div>
</div>
<div class="o_shuttle_actions">
<div class="o_shuttle_content">
<button name="action_menu" type="object"
class="btn-secondary o_shuttle_icon_btn"
string="" icon="fa-bars"
aria-label="Dropdown menu" title="Dropdown menu"/>
<button name="action_manual_barcode" type="object"
class="btn-secondary o_shuttle_icon_btn"
string="" icon="fa-terminal"
aria-label="Barcode Input" title="Barcode Input"/>
</div>
<div class="o_shuttle_content o_shuttle_content_right">
<div>
<!-- will react on barcode 'O-BTN.save' -->
<button name="button_save"
type="object"
string="Save"
icon="fa-check"
class="btn-primary"
barcode_trigger="save"/>
<!-- will react on barcode 'O-BTN.release -->
<button name="button_release"
type="object"
string="Release"
class="btn-primary"
barcode_trigger="release"/>
</div>
</div>
</div>
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
<div class="container">
<field name="operation_descr"/>
</div>
</div>
<div class="o_shuttle_data" attrs="{'invisible': [('current_move_line_id', '=', False)]}">
<!-- on the left of the screen -->
<div class="o_shuttle_data_content o_shuttle_move">
<div>
<group>
<field name="current_move_line_id" invisible="1"/>
<label for="picking_id"/>
<div>
<div>
<field name="picking_id" options="{'no_open': True}" class="mr8"/>
<span>/</span>
<field name="picking_origin" class="oe_inline ml8"/>
</div>
</div>
<label for="picking_partner_id"/>
<div>
<field name="picking_partner_id" options="{'no_open': True}"/>
</div>
<label for="location_dest_id"/>
<div>
<field name="location_dest_id"
class="bg-primary o_shuttle_highlight"
options="{'no_open': True}"/>
</div>
<label for="product_id"/>
<div colspan="2" class="oe_title">
<h1>
<field name="product_id" options="{'no_open': True}"/>
</h1>
</div>
<div colspan="2">
<field name="product_packagings"/>
</div>
<field name="lot_id"/>
<label for="product_uom_qty" string="Quantity" class="ml32"/>
<div colspan="2" class="ml32">
<h1 class="bg-primary o_shuttle_highlight">
<field name="product_uom_qty" class="oe_inline text-white"/>
<field name="product_uom_id" options="{'no_open': True}" class="oe_inline ml8 text-white"/>
</h1>
</div>
</group>
</div>
</div>
<!-- on the right of the screen -->
<div class="o_shuttle_data_content o_shuttle_tray">
<group col="1">
<field name="tray_name"/>
<field name="tray_type_code"/>
<field name="tray_y"/>
<field name="tray_x"/>
<label for="tray_qty"/>
<div colspan="2" class="oe_title">
<h1>
<field name="tray_qty"/>
</h1>
</div>
</group>
<group>
<div>
<field name="tray_matrix"
widget="location_tray_matrix"
/>
</div>
</group>
</div>
</div>
<field name="_barcode_scanned" widget="barcode_handler"/>
</form>
</field>
</record>
<record id="vertical_lift_shuttle_form_menu" model="ir.ui.view">
<field name="name">vertical.lift.shuttle.view.form.menu</field>
<field name="model">vertical.lift.shuttle</field>
@@ -190,9 +63,6 @@
<field name="mode"/>
<field name="location_id"/>
<field name="hardware"/>
<!-- move line visible and editable for prototyping purposes, to
remove later -->
<field name="current_move_line_id" groups="base.group_no_one"/>
</group>
</form>
</field>
@@ -205,8 +75,8 @@ remove later -->
<kanban class="o_kanban_dashboard">
<field name="name"/>
<field name="mode"/>
<field name="number_of_ops"/>
<field name="number_of_ops_all"/>
<!-- <field name="number_of_ops"/> -->
<!-- <field name="number_of_ops_all"/> -->
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click o_has_icon open_shuttle_screen">
@@ -224,14 +94,14 @@ remove later -->
Mode:
<field name="mode"/>
</div>
<div class="col-8">
Operations:
<field name="number_of_ops"/>
</div>
<div class="col-8">
All <t t-esc="record.mode.value"/> Operations:
<field name="number_of_ops_all"/>
</div>
<!-- <div class="col-8"> -->
<!-- Operations: -->
<!-- <field name="number_of_ops"/> -->
<!-- </div> -->
<!-- <div class="col-8"> -->
<!-- All <t t-esc="record.mode.value"/> Operations: -->
<!-- <field name="number_of_ops_all"/> -->
<!-- </div> -->
</div>
</div>
</div>