From 0c9f88d953ede109f44902b7c572b2a8f3373cca Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 15 Oct 2019 15:39:05 +0200 Subject: [PATCH] Implement inventory screen --- stock_vertical_lift/models/__init__.py | 1 + stock_vertical_lift/models/stock_inventory.py | 27 ++ .../models/vertical_lift_operation_base.py | 82 ++++-- .../vertical_lift_operation_inventory.py | 259 +++++++++++++++++- stock_vertical_lift/readme/ROADMAP.rst | 11 +- stock_vertical_lift/tests/common.py | 60 ++++ stock_vertical_lift/tests/test_inventory.py | 117 ++++++-- stock_vertical_lift/tests/test_put.py | 22 +- .../vertical_lift_operation_base_views.xml | 8 +- ...ertical_lift_operation_inventory_views.xml | 62 ++++- 10 files changed, 566 insertions(+), 83 deletions(-) create mode 100644 stock_vertical_lift/models/stock_inventory.py diff --git a/stock_vertical_lift/models/__init__.py b/stock_vertical_lift/models/__init__.py index 839f04888..87a31d40b 100644 --- a/stock_vertical_lift/models/__init__.py +++ b/stock_vertical_lift/models/__init__.py @@ -3,6 +3,7 @@ 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_inventory from . import stock_location from . import stock_move from . import stock_move_line diff --git a/stock_vertical_lift/models/stock_inventory.py b/stock_vertical_lift/models/stock_inventory.py new file mode 100644 index 000000000..8e87e556e --- /dev/null +++ b/stock_vertical_lift/models/stock_inventory.py @@ -0,0 +1,27 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class InventoryLine(models.Model): + _inherit = "stock.inventory.line" + + vertical_lift_done = fields.Boolean(default=False) + # Field used to sort lines by tray on the inventory scan screen, so entire + # trays are processed one after the other + vertical_lift_tray_id = fields.Many2one( + comodel_name="stock.location", + compute="_compute_vertical_lift_tray_id", + readonly=True, + store=True, + ) + + @api.depends("location_id.vertical_lift_kind") + def _compute_vertical_lift_tray_id(self): + for line in self: + if line.location_id.vertical_lift_kind == "cell": + # The parent of the cell is the tray. + line.vertical_lift_tray_id = line.location_id.location_id + else: + line.vertical_lift_tray_id = False diff --git a/stock_vertical_lift/models/vertical_lift_operation_base.py b/stock_vertical_lift/models/vertical_lift_operation_base.py index 8d0656cbc..5c1d09e75 100644 --- a/stock_vertical_lift/models/vertical_lift_operation_base.py +++ b/stock_vertical_lift/models/vertical_lift_operation_base.py @@ -19,6 +19,13 @@ class VerticalLiftOperationBase(models.AbstractModel): location_id = fields.Many2one( related="shuttle_id.location_id", readonly=True ) + 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", + ) mode = fields.Selection(related="shuttle_id.mode", readonly=True) operation_descr = fields.Char( string="Operation", default="...", readonly=True @@ -32,6 +39,16 @@ class VerticalLiftOperationBase(models.AbstractModel): ) ] + @api.depends() + def _compute_number_of_ops(self): + for record in self: + record.number_of_ops = 0 + + @api.depends() + def _compute_number_of_ops_all(self): + for record in self: + record.number_of_ops_all = 0 + def on_barcode_scanned(self, barcode): self.ensure_one() # to implement in sub-classes @@ -51,6 +68,35 @@ class VerticalLiftOperationBase(models.AbstractModel): def action_manual_barcode(self): return self.shuttle_id.action_manual_barcode() + def button_save(self): + """Process the action (pick, put, ...)""" + raise NotImplementedError + + def button_release(self): + """Release the operation, go to the next""" + raise NotImplementedError + + def _render_product_packagings(self, product): + 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 + ) + return content + + def _get_tray_qty(self, product, location): + quants = self.env["stock.quant"].search( + [ + ("location_id", "=", location.id), + ("product_id", "=", product.id), + ] + ) + return sum(quants.mapped("quantity")) + class VerticalLiftOperationTransfer(models.AbstractModel): """Base model for shuttle pick and put operations""" @@ -63,14 +109,6 @@ class VerticalLiftOperationTransfer(models.AbstractModel): comodel_name="stock.move.line", readonly=True ) - 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", @@ -137,21 +175,10 @@ class VerticalLiftOperationTransfer(models.AbstractModel): def _compute_product_packagings(self): for record in self: if not record.current_move_line_id: + record.product_packagings = "" 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 - ) + content = self._render_product_packagings(product) record.product_packagings = content @api.depends() @@ -168,17 +195,12 @@ class VerticalLiftOperationTransfer(models.AbstractModel): def _compute_tray_qty(self): for record in self: if not (record.tray_location_id and record.current_move_line_id): + record.tray_qty = 0. 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")) + location = record.tray_location_id + record.tray_qty = self._get_tray_qty(product, location) - # 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: @@ -245,4 +267,4 @@ class VerticalLiftOperationTransfer(models.AbstractModel): self.operation_descr = _("Release") def fetch_tray(self): - return + raise NotImplementedError diff --git a/stock_vertical_lift/models/vertical_lift_operation_inventory.py b/stock_vertical_lift/models/vertical_lift_operation_inventory.py index 17fd732cd..d1a6088ed 100644 --- a/stock_vertical_lift/models/vertical_lift_operation_inventory.py +++ b/stock_vertical_lift/models/vertical_lift_operation_inventory.py @@ -1,10 +1,261 @@ # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models +from odoo import _, api, fields, models +from odoo.addons.base_sparse_field.models.fields import Serialized +from odoo.tools import float_compare + +# TODO handle autofocus + easy way to validate for the input field class VerticalLiftOperationInventory(models.Model): - _name = 'vertical.lift.operation.inventory' - _inherit = 'vertical.lift.operation.base' - _description = 'Vertical Lift Operation Inventory' + _name = "vertical.lift.operation.inventory" + _inherit = "vertical.lift.operation.base" + _description = "Vertical Lift Operation Inventory" + + current_inventory_line_id = fields.Many2one( + comodel_name="stock.inventory.line", readonly=True + ) + + quantity_input = fields.Float() + # if the quantity is wrong, user has to write 2 times + # the same quantity to really confirm it's correct + last_quantity_input = fields.Float() + state = fields.Selection( + selection=[ + ("quantity", "Inventory, please enter the amount"), + ( + "confirm_wrong_quantity", + "The quantity does not match, are you sure?", + ), + ("save", "Save"), + ], + default="quantity", + ) + + 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 + inventory_id = fields.Many2one( + related="current_inventory_line_id.inventory_id", readonly=True + ) + product_id = fields.Many2one( + related="current_inventory_line_id.product_id", readonly=True + ) + product_uom_id = fields.Many2one( + related="current_inventory_line_id.product_uom_id", readonly=True + ) + product_qty = fields.Float( + related="current_inventory_line_id.product_qty", readonly=True + ) + product_packagings = fields.Html( + string="Packaging", compute="_compute_product_packagings" + ) + package_id = fields.Many2one( + related="current_inventory_line_id.package_id", readonly=True + ) + lot_id = fields.Many2one( + related="current_inventory_line_id.prod_lot_id", readonly=True + ) + + @api.depends("current_inventory_line_id") + def _compute_tray_data(self): + for record in self: + location = record.current_inventory_line_id.location_id + 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 + + @api.depends("current_inventory_line_id.product_id.packaging_ids") + def _compute_product_packagings(self): + for record in self: + if not record.current_inventory_line_id: + record.product_packagings = "" + continue + product = record.current_inventory_line_id.product_id + content = self._render_product_packagings(product) + record.product_packagings = content + + @api.depends("tray_location_id", "current_inventory_line_id.product_id") + def _compute_tray_qty(self): + for record in self: + if not ( + record.tray_location_id and record.current_inventory_line_id + ): + record.tray_qty = 0. + continue + product = record.current_inventory_line_id.product_id + location = record.tray_location_id + record.tray_qty = self._get_tray_qty(product, location) + + def _compute_number_of_ops(self): + for record in self: + line_model = self.env["stock.inventory.line"] + record.number_of_ops = line_model.search_count( + self._domain_inventory_lines_to_do() + ) + + def _compute_number_of_ops_all(self): + for record in self: + line_model = self.env["stock.inventory.line"] + record.number_of_ops_all = line_model.search_count( + self._domain_inventory_lines_to_do_all() + ) + + def _domain_inventory_lines_to_do(self): + return [ + ("location_id", "child_of", self.location_id.id), + ("state", "=", "confirm"), + ("vertical_lift_done", "=", False), + ] + + def _domain_inventory_lines_to_do_all(self): + shuttle_locations = self.env["stock.location"].search( + [("vertical_lift_kind", "=", "view")] + ) + return [ + ("location_id", "child_of", shuttle_locations.ids), + ("state", "=", "confirm"), + ("vertical_lift_done", "=", False), + ] + + def on_screen_open(self): + """Called when the screen is open""" + self.select_next_inventory_line() + + def reset(self): + self.write( + { + "quantity_input": 0., + "last_quantity_input": 0., + "state": "quantity", + } + ) + self.update_step_description() + + def step(self): + return self.state + + def step_to(self, state): + self.state = state + self.update_step_description() + + def step_description(self): + state_field = self._fields["state"] + return state_field.convert_to_export(self.state, self) + + def update_step_description(self): + if self.current_inventory_line_id: + descr = self.step_description() + else: + descr = _("No operations") + self.operation_descr = descr + + def button_save(self): + if not self.current_inventory_line_id: + return + self.ensure_one() + self.process_current() + if self.step() == "save": + self.select_next_inventory_line() + if not self.current_inventory_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 button_release(self): + raise NotImplementedError + + def _has_identical_quantity(self): + line = self.current_inventory_line_id + return ( + float_compare( + line.theoretical_qty, + self.quantity_input, + precision_rounding=line.product_uom_id.rounding, + ) + == 0 + ) + + def _process_quantity(self): + if self.step() == "quantity": + if self._has_identical_quantity(): + self.step_to("save") + return True + else: + self.last_quantity_input = self.quantity_input + self.quantity_input = 0. + self.step_to("confirm_wrong_quantity") + return False + if self.step() == "confirm_wrong_quantity": + if self.quantity_input == self.last_quantity_input: + # confirms the previous input + self.step_to("save") + return True + else: + # cycle back to the first quantity check + self.step_to("quantity") + return self._process_quantity() + + def process_current(self): + line = self.current_inventory_line_id + if self._process_quantity() and not line.vertical_lift_done: + line.vertical_lift_done = True + if self.quantity_input != line.product_qty: + line.product_qty = self.quantity_input + inventory = line.inventory_id + if all(line.vertical_lift_done for line in inventory.line_ids): + inventory.action_validate() + + def fetch_tray(self): + location = self.current_inventory_line_id.location_id + location.fetch_vertical_lift_tray() + + def select_next_inventory_line(self): + self.ensure_one() + next_line = self.env["stock.inventory.line"].search( + self._domain_inventory_lines_to_do(), + limit=1, + order="vertical_lift_tray_id, location_id, id", + ) + previous_line = self.current_inventory_line_id + self.current_inventory_line_id = next_line + self.reset() + if ( + next_line + and previous_line.vertical_lift_tray_id + != next_line.vertical_lift_tray_id + ): + self.fetch_tray() diff --git a/stock_vertical_lift/readme/ROADMAP.rst b/stock_vertical_lift/readme/ROADMAP.rst index c56a03077..84079ec4c 100644 --- a/stock_vertical_lift/readme/ROADMAP.rst +++ b/stock_vertical_lift/readme/ROADMAP.rst @@ -1,3 +1,8 @@ -* Complete Pick screen and workflow (currently enough for a demo, not for production) -* Implement Put-away screen and workflow -* Implement Inventory screen and workflow +* Complete screen workflows (currently enough for a demo, not for production) +* Inventory: find a way to have a nice autofocus for quantity, still compatible + with barcode scanner (Odoo disables the autofocus when using barcode, which + makes sense) +* Put-away: handle packages +* Handle "multi-shuttle" put-away +* Create glue module for product_expiry +* Challenge the save + release buttons and workflow diff --git a/stock_vertical_lift/tests/common.py b/stock_vertical_lift/tests/common.py index f40456ffc..0a6b613bc 100644 --- a/stock_vertical_lift/tests/common.py +++ b/stock_vertical_lift/tests/common.py @@ -21,6 +21,47 @@ class VerticalLiftCase(common.LocationTrayTypeCase): cls.vertical_lift_loc = cls.env.ref( 'stock_vertical_lift.stock_location_vertical_lift' ) + cls.location_1a = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1a" + ) + cls.location_1a_x1y1 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1a_x1y1" + ) + cls.location_1a_x2y1 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1a_x2y1" + ) + cls.location_1a_x3y1 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1a_x3y1" + ) + cls.location_1a_x1y2 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1a_x1y2" + ) + cls.location_1b_x1y1 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1b_x1y1" + ) + cls.location_1b_x1y2 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_1b_x1y2" + ) + cls.location_2a = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_2a" + ) + cls.location_2a_x1y1 = cls.env.ref( + "stock_vertical_lift." + "stock_location_vertical_lift_demo_tray_2a_x1y1" + ) + + def _update_qty_in_location(self, location, product, quantity): + self.env["stock.quant"]._update_available_quantity( + product, location, quantity + ) @classmethod def _create_simple_picking_out(cls, product, quantity): @@ -81,6 +122,25 @@ class VerticalLiftCase(common.LocationTrayTypeCase): } ) + @classmethod + def _create_inventory(self, products): + """Create a draft inventory + + Products is a list of tuples (bin location, product). + """ + values = { + 'name': 'Test Inventory', + 'filter': 'partial', + 'line_ids': [(0, 0, { + 'product_id': product.id, + 'product_uom_id': product.uom_id.id, + 'location_id': location.id + }) for location, product in products] + } + inventory = self.env['stock.inventory'].create(values) + inventory.action_start() + return inventory + def _test_button_release(self, move_line): # for the test, we'll consider our last line has been delivered move_line.qty_done = move_line.product_qty diff --git a/stock_vertical_lift/tests/test_inventory.py b/stock_vertical_lift/tests/test_inventory.py index 864c022ba..68caaf609 100644 --- a/stock_vertical_lift/tests/test_inventory.py +++ b/stock_vertical_lift/tests/test_inventory.py @@ -1,31 +1,110 @@ # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import unittest - +from odoo import _ from .common import VerticalLiftCase class TestInventory(VerticalLiftCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.picking_out = cls.env.ref( - "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 - cls.out_move_line = cls.picking_out.move_line_ids - def test_switch_inventory(self): self.shuttle.switch_inventory() self.assertEqual(self.shuttle.mode, "inventory") + self.assertEqual( + self.shuttle._operation_for_mode().current_inventory_line_id, + self.env["stock.inventory.line"].browse(), + ) - @unittest.skip("Not implemented") - def test_inventory_count_move_lines(self): - pass - - @unittest.skip("Not implemented") - def test_process_current_inventory(self): - # test to implement when the code is implemented + def test_inventory_action_open_screen(self): self.shuttle.switch_inventory() + action = self.shuttle.action_open_screen() + operation = self.shuttle._operation_for_mode() + self.assertEqual(action["type"], "ir.actions.act_window") + self.assertEqual( + action["res_model"], "vertical.lift.operation.inventory" + ) + self.assertEqual(action["res_id"], operation.id) + + def test_inventory_count_ops(self): + self._update_qty_in_location( + self.location_1a_x1y1, self.product_socks, 10 + ) + self._update_qty_in_location( + self.location_1a_x2y1, self.product_recovery, 10 + ) + self._create_inventory( + [ + (self.location_1a_x1y1, self.product_socks), + (self.location_1a_x2y1, self.product_recovery), + ] + ) + self._update_qty_in_location( + self.location_2a_x1y1, self.product_socks, 10 + ) + self._create_inventory([(self.location_2a_x1y1, self.product_socks)]) + + self.shuttle.switch_inventory() + operation = self.shuttle._operation_for_mode() + self.assertEqual(operation.number_of_ops, 2) + self.assertEqual(operation.number_of_ops_all, 3) + + def test_process_current_inventory(self): + self._update_qty_in_location( + self.location_1a_x1y1, self.product_socks, 10 + ) + inventory = self._create_inventory( + [(self.location_1a_x1y1, self.product_socks)] + ) + self.shuttle.switch_inventory() + operation = self.shuttle._operation_for_mode() + self.assertEqual( + operation.current_inventory_line_id, inventory.line_ids + ) + # test the happy path, quantity is correct + operation.quantity_input = 10.0 + result = operation.button_save() + # state is reset + self.assertEqual(operation.state, "quantity") + self.assertFalse(operation.current_inventory_line_id) + self.assertEqual(operation.operation_descr, _("No operations")) + self.assertTrue(inventory.line_ids.vertical_lift_done) + self.assertEqual(inventory.state, "done") + expected_result = { + "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_wrong_quantity(self): + self._update_qty_in_location( + self.location_1a_x1y1, self.product_socks, 10 + ) + inventory = self._create_inventory( + [(self.location_1a_x1y1, self.product_socks)] + ) + self.shuttle.switch_inventory() + operation = self.shuttle._operation_for_mode() + line = operation.current_inventory_line_id + self.assertEqual(line, inventory.line_ids) + + operation.quantity_input = 12.0 + operation.button_save() + self.assertEqual(operation.last_quantity_input, 12.0) + self.assertEqual(operation.quantity_input, 0.0) + self.assertEqual(operation.state, "confirm_wrong_quantity") + self.assertEqual(operation.current_inventory_line_id, line) + self.assertEqual( + operation.operation_descr, + _("The quantity does not match, are you sure?"), + ) + + # entering the same quantity a second time validates + operation.quantity_input = 12.0 + operation.button_save() + self.assertFalse(operation.current_inventory_line_id) + + self.assertTrue(inventory.line_ids.vertical_lift_done) + self.assertEqual(inventory.state, "done") diff --git a/stock_vertical_lift/tests/test_put.py b/stock_vertical_lift/tests/test_put.py index f13038906..d3f391100 100644 --- a/stock_vertical_lift/tests/test_put.py +++ b/stock_vertical_lift/tests/test_put.py @@ -13,22 +13,6 @@ class TestPut(VerticalLiftCase): ) cls.picking_in.action_confirm() cls.in_move_line = cls.picking_in.move_line_ids - cls.location_1a_x1y1 = cls.env.ref( - "stock_vertical_lift." - "stock_location_vertical_lift_demo_tray_1a_x1y1" - ) - cls.location_1a_x2y1 = cls.env.ref( - "stock_vertical_lift." - "stock_location_vertical_lift_demo_tray_1a_x2y1" - ) - cls.location_1a_x3y1 = cls.env.ref( - "stock_vertical_lift." - "stock_location_vertical_lift_demo_tray_1a_x3y1" - ) - cls.location_1a_x1y2 = cls.env.ref( - "stock_vertical_lift." - "stock_location_vertical_lift_demo_tray_1a_x1y2" - ) cls.in_move_line.location_dest_id = cls.location_1a_x3y1 def _select_move_lines(self, shuttle, move_lines=None): @@ -107,10 +91,6 @@ class TestPut(VerticalLiftCase): def test_put_count_move_lines(self): self.shuttle.switch_put() self.picking_in.action_cancel() - location_2a_x1y1 = self.env.ref( - "stock_vertical_lift." - "stock_location_vertical_lift_demo_tray_2a_x1y1" - ) put1 = self._create_simple_picking_in( self.product_socks, 10, self.location_1a_x1y1 ) @@ -120,7 +100,7 @@ class TestPut(VerticalLiftCase): ) put2.action_confirm() put3 = self._create_simple_picking_in( - self.product_recovery, 10, location_2a_x1y1 + self.product_recovery, 10, self.location_2a_x1y1 ) put3.action_confirm() operation = self.shuttle._operation_for_mode() diff --git a/stock_vertical_lift/views/vertical_lift_operation_base_views.xml b/stock_vertical_lift/views/vertical_lift_operation_base_views.xml index 60457ce93..fa665be90 100644 --- a/stock_vertical_lift/views/vertical_lift_operation_base_views.xml +++ b/stock_vertical_lift/views/vertical_lift_operation_base_views.xml @@ -37,6 +37,9 @@
+
@@ -86,11 +89,6 @@ 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 index b6ae1de6a..27ebe31fd 100644 --- a/stock_vertical_lift/views/vertical_lift_operation_inventory_views.xml +++ b/stock_vertical_lift/views/vertical_lift_operation_inventory_views.xml @@ -23,8 +23,68 @@
Inventory Screen
+ +
+ + + + + + + {'invisible': [('current_inventory_line_id', '=', False)]} + - Not implemented + +
+
+ + +
+
+ +
+
+
+
+
+ +
+ + + + + + + +
+ +
+
+