diff --git a/stock_vertical_lift/__manifest__.py b/stock_vertical_lift/__manifest__.py
index 580fc0706..7d97d1741 100644
--- a/stock_vertical_lift/__manifest__.py
+++ b/stock_vertical_lift/__manifest__.py
@@ -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',
diff --git a/stock_vertical_lift/models/__init__.py b/stock_vertical_lift/models/__init__.py
index 80b7355db..839f04888 100644
--- a/stock_vertical_lift/models/__init__.py
+++ b/stock_vertical_lift/models/__init__.py
@@ -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
diff --git a/stock_vertical_lift/models/stock_move.py b/stock_vertical_lift/models/stock_move.py
index 1210d6b09..42222e716 100644
--- a/stock_vertical_lift/models/stock_move.py
+++ b/stock_vertical_lift/models/stock_move.py
@@ -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
diff --git a/stock_vertical_lift/models/stock_quant.py b/stock_vertical_lift/models/stock_quant.py
index 905484139..5a5ba4f86 100644
--- a/stock_vertical_lift/models/stock_quant.py
+++ b/stock_vertical_lift/models/stock_quant.py
@@ -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
diff --git a/stock_vertical_lift/models/vertical_lift_operation_base.py b/stock_vertical_lift/models/vertical_lift_operation_base.py
new file mode 100644
index 000000000..a23cdc2ec
--- /dev/null
+++ b/stock_vertical_lift/models/vertical_lift_operation_base.py
@@ -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()
diff --git a/stock_vertical_lift/models/vertical_lift_operation_inventory.py b/stock_vertical_lift/models/vertical_lift_operation_inventory.py
new file mode 100644
index 000000000..17fd732cd
--- /dev/null
+++ b/stock_vertical_lift/models/vertical_lift_operation_inventory.py
@@ -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'
diff --git a/stock_vertical_lift/models/vertical_lift_operation_pick.py b/stock_vertical_lift/models/vertical_lift_operation_pick.py
new file mode 100644
index 000000000..25bd40173
--- /dev/null
+++ b/stock_vertical_lift/models/vertical_lift_operation_pick.py
@@ -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()
diff --git a/stock_vertical_lift/models/vertical_lift_operation_put.py b/stock_vertical_lift/models/vertical_lift_operation_put.py
new file mode 100644
index 000000000..92903fb01
--- /dev/null
+++ b/stock_vertical_lift/models/vertical_lift_operation_put.py
@@ -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"))
diff --git a/stock_vertical_lift/models/vertical_lift_shuttle.py b/stock_vertical_lift/models/vertical_lift_shuttle.py
index fcdb4a3bd..d3271acb2 100644
--- a/stock_vertical_lift/models/vertical_lift_shuttle.py
+++ b/stock_vertical_lift/models/vertical_lift_shuttle.py
@@ -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)
diff --git a/stock_vertical_lift/security/ir.model.access.csv b/stock_vertical_lift/security/ir.model.access.csv
index dc7cb4b87..2a69468e4 100644
--- a/stock_vertical_lift/security/ir.model.access.csv
+++ b/stock_vertical_lift/security/ir.model.access.csv
@@ -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
diff --git a/stock_vertical_lift/tests/test_vertical_lift_shuttle.py b/stock_vertical_lift/tests/test_vertical_lift_shuttle.py
index 1575051cb..b80e46c1e 100644
--- a/stock_vertical_lift/tests/test_vertical_lift_shuttle.py
+++ b/stock_vertical_lift/tests/test_vertical_lift_shuttle.py
@@ -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)
diff --git a/stock_vertical_lift/views/vertical_lift_operation_base_views.xml b/stock_vertical_lift/views/vertical_lift_operation_base_views.xml
new file mode 100644
index 000000000..752dd5940
--- /dev/null
+++ b/stock_vertical_lift/views/vertical_lift_operation_base_views.xml
@@ -0,0 +1,147 @@
+
+
+
+
+ vertical.lift.operation.base.screen.view
+ vertical.lift.operation.base
+
+
+
+
+
+
+ vertical.lift.operation.transfer.screen.view
+ vertical.lift.operation.transfer
+
+ primary
+
+
+
+ /
+
+
+
+ {'invisible': [('current_move_line_id', '=', False)]}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stock_vertical_lift/views/vertical_lift_operation_inventory_views.xml b/stock_vertical_lift/views/vertical_lift_operation_inventory_views.xml
new file mode 100644
index 000000000..509b471c8
--- /dev/null
+++ b/stock_vertical_lift/views/vertical_lift_operation_inventory_views.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ vertical.lift.operation.inventory.screen.view
+ vertical.lift.operation.inventory
+
+ primary
+
+
+
+ Not implemented
+
+
+
+
+
diff --git a/stock_vertical_lift/views/vertical_lift_operation_pick_views.xml b/stock_vertical_lift/views/vertical_lift_operation_pick_views.xml
new file mode 100644
index 000000000..2fc407f9e
--- /dev/null
+++ b/stock_vertical_lift/views/vertical_lift_operation_pick_views.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ vertical.lift.operation.pick.screen.view
+ vertical.lift.operation.pick
+
+ primary
+
+
+
+
+
+
diff --git a/stock_vertical_lift/views/vertical_lift_operation_put_views.xml b/stock_vertical_lift/views/vertical_lift_operation_put_views.xml
new file mode 100644
index 000000000..4a891d985
--- /dev/null
+++ b/stock_vertical_lift/views/vertical_lift_operation_put_views.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ vertical.lift.operation.put.screen.view
+ vertical.lift.operation.put
+
+ primary
+
+
+
+
+
+
diff --git a/stock_vertical_lift/views/vertical_lift_shuttle_views.xml b/stock_vertical_lift/views/vertical_lift_shuttle_views.xml
index 6d5bb6d53..97169a43c 100644
--- a/stock_vertical_lift/views/vertical_lift_shuttle_views.xml
+++ b/stock_vertical_lift/views/vertical_lift_shuttle_views.xml
@@ -1,133 +1,6 @@
-
- vertical.lift.shuttle.view.form.screen
- vertical.lift.shuttle
- 99
-
-
-
-
-