Merge PR #1090 into 14.0

Signed-off-by simahawk
This commit is contained in:
OCA-git-bot
2021-09-17 12:58:20 +00:00
58 changed files with 5582 additions and 0 deletions

View File

@@ -0,0 +1 @@
../../../../stock_vertical_lift

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@@ -0,0 +1,167 @@
=============
Vertical Lift
=============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift
:alt: OCA/stock-logistics-warehouse
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_vertical_lift
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/153/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
Add configuration and dedicated screens to work with Vertical Lift
systems (such as Kardex Remstar, Modula, ...). Drivers for controlling
the lifts physically must be added by additional addons.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Configuration
=============
General
~~~~~~~
In Inventory Settings, you must have:
* Storage Locations
* Multi-Warehouses
* Multi-Step Routes
Locations
~~~~~~~~~
Additional configuration parameters are added in Locations:
* Sub-locations of a location with the "Is a Vertical Lift View Location"
activated are considered as "Shuttles". A shuttle is a vertical lift shelf.
* Sub-locations of shuttles are considered as "Trays", which is a tier of a
shuttle. When a tray is created, a tray type must be selected. When saved, the
tray location will automatically create as many sub-locations - called
"Cells" - as the tray type contains.
* The tray type of a tray can be changed as long as none of its cell contains
products. When changed, it archives the cells and creates new ones as
configured on the new tray type.
Tray types
~~~~~~~~~~
Tray types can be configured in the Inventory settings.
A tray type defines how much cells a tray can hold. It is a square or rectangle
matrix of n cols * m rows.
Vertical Lift Shuttles
~~~~~~~~~~~~~~~~~~~~~~
The Shuttles are the Vertical Lift Trays. One Shuttle entity has to be created
in Odoo for each physical shuttle. Depending of the subsidiary addons installed
(eg. Kardex), different options may be required (host address, ...). The base
addon only includes shuttles of kind "simulation" which will not send orders to
the hardware.
Put-away configuration
~~~~~~~~~~~~~~~~~~~~~~
If you want to use put-away in the vertical lift, the Receipts must have the
vertical lift view as destination. E.g. create put-away rules on the products
so when they arrive in WH/Stock, they are stored in WH/Stock/Vertical Lift. On
the put-away screen, when scanning the tray type to store, the destination will
be updated with an available cell of the same tray type in the current shuttle.
Barcodes
~~~~~~~~
The operations allowed in the screen for the vertical lift (save, release, skip)
can be triggered using a barcode. For this, print the barcodes contained in the
folder 'images'.
Development
===========
The barcodes used are of the type Code 128 (with the code set B).
Known issues / Roadmap
======================
* 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
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-warehouse/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_vertical_lift%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Camptocamp
Contributors
~~~~~~~~~~~~
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
Trobz
* Dung Tran <dungtd@trobz.com>
Other credits
~~~~~~~~~~~~~
The development of this module has been financially supported by:
* Camptocamp
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,3 @@
from . import models
from . import controllers
from . import wizards

View File

@@ -0,0 +1,41 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Vertical Lift",
"summary": "Provides the core for integration with Vertical Lifts",
"version": "14.0.1.0.0",
"category": "Stock",
"author": "Camptocamp, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": [
"stock",
"barcodes",
"base_sparse_field",
"stock_location_tray", # OCA/stock-logistics-warehouse
"web_notify", # OCA/web
"web_ir_actions_act_view_reload", # OCA/web
],
"website": "https://github.com/OCA/stock-logistics-warehouse",
"demo": [
"demo/stock_location_demo.xml",
"demo/vertical_lift_shuttle_demo.xml",
"demo/product_demo.xml",
"demo/stock_inventory_demo.xml",
"demo/stock_picking_demo.xml",
],
"data": [
"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",
"data/ir_sequence.xml",
],
"installable": True,
"development_status": "Alpha",
}

View File

@@ -0,0 +1 @@
from . import main

View File

@@ -0,0 +1,22 @@
import logging
import os
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class VerticalLiftController(http.Controller):
@http.route(["/vertical-lift"], type="http", auth="public", csrf=False)
def vertical_lift(self, answer, secret):
if secret == os.environ.get("VERTICAL_LIFT_SECRET", ""):
rec = request.env["vertical.lift.command"].sudo().record_answer(answer)
return str(rec.id)
else:
_logger.error(
"secret mismatch: %r != %r",
secret,
os.environ.get("VERTICAL_LIFT_SECRET", ""),
)
raise http.AuthenticationError()

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record model="ir.sequence" id="sequence_kardex_command">
<field name="name">Vertical Lift Commands</field>
<field name="code">vertical.lift.command</field>
<field name="prefix">L</field>
<field name="padding">6</field>
<field name="company_id" eval="False" />
</record>
</odoo>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="product_running_socks" model="product.product">
<field name="default_code">RS200</field>
<field name="barcode">4491673293664</field>
<field name="name">Running Socks</field>
<field name="type">product</field>
<field name="categ_id" ref="product.product_category_6" />
<field name="lst_price">30.0</field>
<field name="standard_price">20.0</field>
<field name="weight">1.0</field>
<field name="tracking">none</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
</record>
<record id="product_recovery_socks" model="product.product">
<field name="default_code">RS300</field>
<field name="barcode">2779891103531</field>
<field name="name">Recovery Socks</field>
<field name="type">product</field>
<field name="categ_id" ref="product.product_category_6" />
<field name="lst_price">30.0</field>
<field name="standard_price">20.0</field>
<field name="weight">1.0</field>
<field name="tracking">none</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
</record>
</odoo>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_inventory_vertical_lift_0" model="stock.inventory">
<field name="name">Starting Vertical Lift Inventory</field>
</record>
<record id="stock_inventory_vertical_lift_line_1" model="stock.inventory.line">
<field name="product_id" ref="product_running_socks" />
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="inventory_id" ref="stock_inventory_vertical_lift_0" />
<field name="product_qty">10.0</field>
<field
name="location_id"
ref="stock_location_vertical_lift_demo_tray_1b_x1y2"
/>
</record>
<record id="stock_inventory_vertical_lift_line_2" model="stock.inventory.line">
<field name="product_id" ref="product_running_socks" />
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="inventory_id" ref="stock_inventory_vertical_lift_0" />
<field name="product_qty">10.0</field>
<field
name="location_id"
ref="stock_location_vertical_lift_demo_tray_1b_x2y2"
/>
</record>
<record id="stock_inventory_vertical_lift_line_3" model="stock.inventory.line">
<field name="product_id" ref="product_running_socks" />
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="inventory_id" ref="stock_inventory_vertical_lift_0" />
<field name="product_qty">10.0</field>
<field
name="location_id"
ref="stock_location_vertical_lift_demo_tray_1b_x3y2"
/>
</record>
<function model="stock.inventory" name="_action_start">
<function
eval="[[('state','=','draft'),('id', '=', ref('stock_vertical_lift.stock_inventory_vertical_lift_0'))]]"
model="stock.inventory"
name="search"
/>
</function>
<function model="stock.inventory" name="action_validate">
<function
eval="[[('state','=','confirm'),('id', '=', ref('stock_vertical_lift.stock_inventory_vertical_lift_0'))]]"
model="stock.inventory"
name="search"
/>
</function>
</odoo>

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_location_vertical_lift" model="stock.location">
<field name="name">Vertical Lift</field>
<!-- Beware, must not be view, even if we must not have stock here.
Because stock moves targeting views are not included in the
inventory at date, and we want to be able to use "Vertical Lift View"
as destination (the final destination will be a "cell" in a Shuttle).
-->
<field name="location_id" ref="stock.stock_location_stock" />
<field name="usage">internal</field>
<field name="vertical_lift_location" eval="True" />
</record>
<record id="stock_location_vertical_lift_demo_shuttle_1" model="stock.location">
<field name="name">Shuttle 1</field>
<field name="location_id" ref="stock_location_vertical_lift" />
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_1a" model="stock.location">
<field name="name">Tray 1A</field>
<field name="barcode">T1A</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_1" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_small_8x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_1b" model="stock.location">
<field name="name">Tray 1B</field>
<field name="barcode">T1B</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_1" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_8x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_1c" model="stock.location">
<field name="name">Tray 1C</field>
<field name="barcode">T1C</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_1" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_32x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_shuttle_2" model="stock.location">
<field name="name">Shuttle 2</field>
<field name="location_id" ref="stock_location_vertical_lift" />
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_2a" model="stock.location">
<field name="name">Tray 2A</field>
<field name="barcode">T2A</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_2" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_8x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_2b" model="stock.location">
<field name="name">Tray 2B</field>
<field name="barcode">T2B</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_2" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_4x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_2c" model="stock.location">
<field name="name">Tray 2C</field>
<field name="barcode">T2C</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_2" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_16x_2"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_2d" model="stock.location">
<field name="name">Tray 2D</field>
<field name="barcode">T2D</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_2" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_small_8x"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_shuttle_3" model="stock.location">
<field name="name">Shuttle 3</field>
<field name="location_id" ref="stock_location_vertical_lift" />
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_3a" model="stock.location">
<field name="name">Tray 3A</field>
<field name="barcode">T3A</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_3" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_small_16x_2"
/>
<field name="usage">internal</field>
</record>
<record id="stock_location_vertical_lift_demo_tray_3b" model="stock.location">
<field name="name">Tray 3B</field>
<field name="barcode">T3B</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_3" />
<field
name="tray_type_id"
ref="stock_location_tray.stock_location_tray_type_large_32x"
/>
<field name="usage">internal</field>
</record>
<!-- When the trays are created, they will create their 'cell' locations.
This method will add xmlids on them to be able to reference them in
other demo data and tests.
-->
<function model="stock.location" name="_create_tray_xmlids">
<function
eval="[[('cell_in_tray_type_id', '!=', False)]]"
model="stock.location"
name="search"
/>
<value>stock_vertical_lift</value>
</function>
</odoo>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_picking_out_demo_vertical_lift_1" model="stock.picking">
<field name="picking_type_id" ref="stock.picking_type_out" />
<field name="origin">Outgoing shipment from Vertical Lift (demo)</field>
<field name="partner_id" ref="base.res_partner_1" />
<field name="date" eval="DateTime.today()" />
<field name="location_id" ref="stock.stock_location_stock" />
<field name="location_dest_id" ref="stock.stock_location_customers" />
<field
name="move_lines"
model="stock.move"
eval="[(0, 0, {
'name': obj().env.ref('stock_vertical_lift.product_running_socks').name,
'product_id': ref('stock_vertical_lift.product_running_socks'),
'product_uom': ref('uom.product_uom_unit'),
'product_uom_qty': 15.0,
'picking_type_id': ref('stock.picking_type_out'),
'location_id': ref('stock.stock_location_stock'),
'location_dest_id': ref('stock.stock_location_customers'),
})]"
/>
</record>
<function model="stock.picking" name="action_confirm">
<value
model="stock.picking"
eval="[obj().env.ref('stock_vertical_lift.stock_picking_out_demo_vertical_lift_1').id]"
/>
</function>
<function model="stock.picking" name="action_assign">
<value
model="stock.picking"
eval="[obj().env.ref('stock_vertical_lift.stock_picking_out_demo_vertical_lift_1').id]"
/>
</function>
<record id="stock_picking_in_demo_vertical_lift_1" model="stock.picking">
<field name="picking_type_id" ref="stock.picking_type_in" />
<field name="origin">Incoming shipment from Vertical Lift (demo)</field>
<field name="partner_id" ref="base.res_partner_1" />
<field name="date" eval="DateTime.today()" />
<field name="location_id" ref="stock.stock_location_suppliers" />
<field name="location_dest_id" ref="stock.stock_location_stock" />
<field
name="move_lines"
model="stock.move"
eval="[(0, 0, {
'name': obj().env.ref('stock_vertical_lift.product_running_socks').name,
'product_id': ref('stock_vertical_lift.product_running_socks'),
'product_uom': ref('uom.product_uom_unit'),
'product_uom_qty': 15.0,
'picking_type_id': ref('stock.picking_type_in'),
'location_id': ref('stock.stock_location_suppliers'),
'location_dest_id': ref('stock.stock_location_stock'),
})]"
/>
</record>
<function model="stock.picking" name="action_confirm">
<value
model="stock.picking"
eval="[obj().env.ref('stock_vertical_lift.stock_picking_in_demo_vertical_lift_1').id]"
/>
</function>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_vertical_lift_demo_shuttle_1" model="vertical.lift.shuttle">
<field name="name">Shuttle 1</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_1" />
<field name="mode">pick</field>
</record>
<record id="stock_vertical_lift_demo_shuttle_2" model="vertical.lift.shuttle">
<field name="name">Shuttle 2</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_2" />
<field name="mode">pick</field>
</record>
<record id="stock_vertical_lift_demo_shuttle_3" model="vertical.lift.shuttle">
<field name="name">Shuttle 3</field>
<field name="location_id" ref="stock_location_vertical_lift_demo_shuttle_3" />
<field name="mode">pick</field>
</record>
</odoo>

View File

@@ -0,0 +1,818 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_vertical_lift
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/stock_location.py:0
#, python-format
msgid "<br/>Laser pointer on x{} y{} ({}mm, {}mm)"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_kanban
msgid "<i class=\"fa fa-ellipsis-v\" role=\"img\" aria-label=\"Manage\" title=\"Manage\"/>"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_shuttle_manual_barcode
msgid "Action to input a barcode"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__answer
msgid "Answer"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_form_menu
msgid "Back to settings"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_shuttle.py:0
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__barcode
#, python-format
msgid "Barcode"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_screen_view
msgid "Barcode Input"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base___barcode_scanned
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory___barcode_scanned
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick___barcode_scanned
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put___barcode_scanned
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer___barcode_scanned
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle___barcode_scanned
msgid "Barcode Scanned"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/stock_location.py:0
#, python-format
msgid "Cannot fetch a vertical lift tray on location %s"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__stock_location__vertical_lift_kind__cell
msgid "Cell"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_matrix
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_matrix
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_matrix
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_matrix
msgid "Cells"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_stock_location__vertical_lift_location
msgid "Check this box to use it as the view for Vertical Lift Shuttles."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__command
msgid "Command"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_manual_barcode_form
msgid "Confirm"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_base.py:0
#: code:addons/stock_vertical_lift/tests/common.py:0
#: code:addons/stock_vertical_lift/tests/test_inventory.py:0
#, python-format
msgid "Congrats, you cleared the queue!"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__picking_partner_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__picking_partner_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__picking_partner_id
msgid "Contact"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__product_qty
msgid "Counted Quantity"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__create_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__create_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__create_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__create_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__create_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__create_uid
msgid "Created by"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_base.py:0
#, python-format
msgid ""
"Created from backorder <a href=# data-oe-model=stock.picking data-oe-"
"id=%d>%s</a>."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__create_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__create_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__create_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__create_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__create_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__create_date
msgid "Created on"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__current_inventory_line_id
msgid "Current Inventory Line"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__current_move_line_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__current_move_line_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__current_move_line_id
msgid "Current Move Line"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__location_dest_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__location_dest_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__location_dest_id
msgid "Destination"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__display_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__display_name
msgid "Display Name"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__qty_done
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__qty_done
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__qty_done
msgid "Done"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_screen_view
msgid "Dropdown menu"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__error
msgid "Error"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.view_stock_move_line_operation_tree
msgid "Fetch Destination Tray"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.view_location_form
msgid "Fetch Shuttle Tray"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.view_stock_move_line_operation_tree
msgid "Fetch Source Tray"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__hardware
msgid "Hardware"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__command_ids
msgid "Hardware commands"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__id
msgid "ID"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_stock_move_line__vertical_lift_skipped
msgid ""
"If this flag is set, it means that when the move was being processed in the "
"Vertical Lift, the operator decided to skip its processing."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__inventory_id
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__vertical_lift_shuttle__mode__inventory
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_form_menu
msgid "Inventory"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_stock_inventory_line
msgid "Inventory Line"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_stock_location
msgid "Inventory Locations"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_inventory_button_view
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_inventory_screen_view
msgid "Inventory Screen"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_location__inverse_vertical_lift_shuttle_ids
msgid "Inverse Vertical Lift Shuttle"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_location__vertical_lift_location
msgid "Is a Vertical Lift View Location?"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle____last_update
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode____last_update
msgid "Last Modified on"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__last_quantity_input
msgid "Last Quantity Input"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__write_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__write_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__write_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__write_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__write_uid
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__write_uid
msgid "Last Updated by"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__write_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__write_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__write_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__write_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__write_date
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle_manual_barcode__write_date
msgid "Last Updated on"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_shuttle.py:0
#, python-format
msgid "Lift Simulation"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__location_id
msgid "Location"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__lot_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__lot_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__lot_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__lot_id
msgid "Lot/Serial Number"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_shuttle.py:0
#, python-format
msgid "Menu"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__mode
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__mode
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__mode
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__mode
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__mode
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__mode
msgid "Mode"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_kanban
msgid "Mode:"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__name
msgid "Name"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_form
msgid "Network"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_put.py:0
#, python-format
msgid "No free space for tray type \"{}\" in this shuttle."
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_pick.py:0
#, python-format
msgid "No location found for barcode {}"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_put.py:0
#, python-format
msgid "No move line found for barcode {}"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_operation_put.py:0
#, python-format
msgid "No tray type found for barcode {}"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__number_of_ops
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__number_of_ops
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__number_of_ops
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__number_of_ops
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__number_of_ops
msgid "Number of Operations"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__number_of_ops_all
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__number_of_ops_all
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__number_of_ops_all
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__number_of_ops_all
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__number_of_ops_all
msgid "Number of Operations in all shuttles"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_operation_base_shuttle_id_unique
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_operation_inventory_shuttle_id_unique
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_operation_pick_shuttle_id_unique
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_operation_put_shuttle_id_unique
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_operation_transfer_shuttle_id_unique
msgid "One pick can be run at a time for a shuttle."
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.actions.act_window,help:stock_vertical_lift.vertical_lift_shuttle_action
msgid "Open the Shuttle Interface."
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/stock_location.py:0
#, python-format
msgid "Opening tray {}."
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_button_view
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_screen_view
msgid "Operations"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__package_id
msgid "Pack"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__product_packagings
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__product_packagings
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__product_packagings
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__product_packagings
msgid "Packaging"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__vertical_lift_shuttle__mode__pick
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_form_menu
msgid "Pick"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_pick_button_view
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_pick_screen_view
msgid "Pick Screen"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__port
msgid "Port"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__product_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__product_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__product_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__product_id
msgid "Product"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_stock_move_line
msgid "Product Moves (Stock Move Line)"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__product_uom_id
msgid "Product Unit of Measure"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__vertical_lift_shuttle__mode__put
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_form_menu
msgid "Put"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_put_button_view
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_put_screen_view
msgid "Put-Away Screen"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_inventory_screen_view
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_transfer_screen_view
msgid "Quantity"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__quantity_input
msgid "Quantity Input"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_stock_quant
msgid "Quants"
msgstr ""
#. module: stock_vertical_lift
#: model:product.product,name:stock_vertical_lift.product_recovery_socks
#: model:product.template,name:stock_vertical_lift.product_recovery_socks_product_template
msgid "Recovery Socks"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_pick__picking_origin
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_put__picking_origin
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_transfer__picking_origin
msgid "Reference of the document"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_screen_view
msgid "Release"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.view_location_form
msgid "Release Shuttle Tray"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_form
msgid "Release tray"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_shuttle.py:0
#, python-format
msgid "Releasing tray"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_button_view
msgid "Reopen Screen"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__product_uom_qty
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__product_uom_qty
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__product_uom_qty
msgid "Reserved"
msgstr ""
#. module: stock_vertical_lift
#: model:product.product,name:stock_vertical_lift.product_running_socks
#: model:product.template,name:stock_vertical_lift.product_running_socks_product_template
msgid "Running Socks"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_base_screen_view
msgid "Save"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__server
msgid "Server"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_kanban
msgid "Settings"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_command__shuttle_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__shuttle_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__shuttle_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__shuttle_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__shuttle_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__shuttle_id
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__stock_location__vertical_lift_kind__shuttle
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_form_menu
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_manual_barcode_form
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_form
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_tree
msgid "Shuttle"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_shuttle_view_form
msgid "Shuttle Name"
msgstr ""
#. module: stock_vertical_lift
#: model_terms:ir.ui.view,arch_db:stock_vertical_lift.vertical_lift_operation_pick_screen_view
msgid "Skip"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_move_line__vertical_lift_skipped
msgid "Skipped in Vertical Lift?"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__picking_origin
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__picking_origin
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__picking_origin
msgid "Source Document"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_base__state
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__state
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__state
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__state
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__state
msgid "State"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_stock_move
msgid "Stock Move"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__picking_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__picking_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__picking_id
msgid "Stock Picking"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_qty
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_qty
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_qty
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_qty
msgid "Stock Quantity"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_base__location_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_inventory__location_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_pick__location_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_put__location_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_transfer__location_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_shuttle__location_id
msgid ""
"The Shuttle source location for Pick operations and destination location for"
" Put operations."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_pick__picking_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_put__picking_id
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_transfer__picking_id
msgid "The stock operation where the packing has been made"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__stock_location__vertical_lift_kind__tray
msgid "Tray"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_type_code
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_type_code
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_type_code
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_type_code
msgid "Tray Code"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_location_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_location_id
msgid "Tray Location"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_name
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_name
msgid "Tray Name"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_inventory__tray_type_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__tray_type_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__tray_type_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__tray_type_id
msgid "Tray Type"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_pick__product_uom_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_put__product_uom_id
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_operation_transfer__product_uom_id
msgid "Unit of Measure"
msgstr ""
#. module: stock_vertical_lift
#: model:product.product,uom_name:stock_vertical_lift.product_recovery_socks
#: model:product.product,uom_name:stock_vertical_lift.product_running_socks
#: model:product.template,uom_name:stock_vertical_lift.product_recovery_socks_product_template
#: model:product.template,uom_name:stock_vertical_lift.product_running_socks_product_template
msgid "Units"
msgstr ""
#. module: stock_vertical_lift
#: code:addons/stock_vertical_lift/models/vertical_lift_command.py:0
#, python-format
msgid "Unknown record %s"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_vertical_lift_shuttle__use_tls
msgid "Use Tls"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_base___barcode_scanned
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_inventory___barcode_scanned
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_pick___barcode_scanned
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_put___barcode_scanned
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_operation_transfer___barcode_scanned
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_shuttle___barcode_scanned
msgid "Value of the last barcode scanned."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_inventory_line__vertical_lift_done
msgid "Vertical Lift Done"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_location__vertical_lift_kind
msgid "Vertical Lift Kind"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_operation_base
msgid "Vertical Lift Operation - Base"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_operation_transfer
msgid "Vertical Lift Operation - Transfer"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_operation_inventory
msgid "Vertical Lift Operation Inventory"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_operation_pick
msgid "Vertical Lift Operation Pick"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_operation_put
msgid "Vertical Lift Operation Put"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_shuttle
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_location__vertical_lift_shuttle_id
msgid "Vertical Lift Shuttle"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.actions.act_window,name:stock_vertical_lift.vertical_lift_shuttle_action
#: model:ir.ui.menu,name:stock_vertical_lift.vertical_lift_shuttle
msgid "Vertical Lift Shuttles"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,field_description:stock_vertical_lift.field_stock_inventory_line__vertical_lift_tray_id
msgid "Vertical Lift Tray"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields.selection,name:stock_vertical_lift.selection__stock_location__vertical_lift_kind__view
msgid "View"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.constraint,message:stock_vertical_lift.constraint_vertical_lift_shuttle_location_id_unique
msgid "You cannot have two shuttles using the same location."
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model,name:stock_vertical_lift.model_vertical_lift_command
msgid "commands sent to the shuttle"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_shuttle__server
msgid "hostname or IP address of the server"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_shuttle__port
msgid "network port of the server on which to send the message"
msgstr ""
#. module: stock_vertical_lift
#: model:ir.model.fields,help:stock_vertical_lift.field_vertical_lift_shuttle__use_tls
msgid "set this if the server expects TLS wrapped communication"
msgstr ""

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" width="356" height="81.587" viewBox="0 0 356 81.587">
<desc></desc>
<rect x="0" y="0" width="356" height="81.587" style="fill:#ffffff;" />
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="2" y1="0" x2="2" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="7" y1="0" x2="7" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="13" y1="0" x2="13" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="23" y1="0" x2="23" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="33" y1="0" x2="33" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="40" y1="0" x2="40" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="45" y1="0" x2="45" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="52" y1="0" x2="52" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="59" y1="0" x2="59" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="67" y1="0" x2="67" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="75" y1="0" x2="75" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="80" y1="0" x2="80" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="90" y1="0" x2="90" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="97" y1="0" x2="97" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="107" y1="0" x2="107" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="111" y1="0" x2="111" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="117" y1="0" x2="117" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="128" y1="0" x2="128" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="133" y1="0" x2="133" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="140" y1="0" x2="140" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="149" y1="0" x2="149" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="155" y1="0" x2="155" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="161" y1="0" x2="161" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="170" y1="0" x2="170" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="177" y1="0" x2="177" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="182" y1="0" x2="182" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="189" y1="0" x2="189" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="200" y1="0" x2="200" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="207" y1="0" x2="207" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="211" y1="0" x2="211" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="221" y1="0" x2="221" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="226" y1="0" x2="226" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="233" y1="0" x2="233" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="243" y1="0" x2="243" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="249" y1="0" x2="249" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="254" y1="0" x2="254" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="265" y1="0" x2="265" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="272" y1="0" x2="272" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="281" y1="0" x2="281" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="287" y1="0" x2="287" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="292" y1="0" x2="292" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="299" y1="0" x2="299" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="309" y1="0" x2="309" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="318" y1="0" x2="318" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="327" y1="0" x2="327" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="332" y1="0" x2="332" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="343" y1="0" x2="343" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="349" y1="0" x2="349" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="354" y1="0" x2="354" y2="60"/>
<text x="178" y="77.271" text-anchor="middle" style="font-family: Tahoma; font-size: 20px; stroke: none; fill:#000000;">O-BTN.release</text></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" width="290" height="81.587" viewBox="0 0 290 81.587">
<desc></desc>
<rect x="0" y="0" width="290" height="81.587" style="fill:#ffffff;" />
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="2" y1="0" x2="2" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="7" y1="0" x2="7" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="13" y1="0" x2="13" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="23" y1="0" x2="23" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="33" y1="0" x2="33" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="40" y1="0" x2="40" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="45" y1="0" x2="45" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="52" y1="0" x2="52" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="59" y1="0" x2="59" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="67" y1="0" x2="67" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="75" y1="0" x2="75" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="80" y1="0" x2="80" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="90" y1="0" x2="90" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="97" y1="0" x2="97" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="107" y1="0" x2="107" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="111" y1="0" x2="111" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="117" y1="0" x2="117" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="128" y1="0" x2="128" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="133" y1="0" x2="133" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="140" y1="0" x2="140" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="149" y1="0" x2="149" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="155" y1="0" x2="155" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="162" y1="0" x2="162" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="171" y1="0" x2="171" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="177" y1="0" x2="177" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="183" y1="0" x2="183" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="188" y1="0" x2="188" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="202" y1="0" x2="202" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="209" y1="0" x2="209" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="215" y1="0" x2="215" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="221" y1="0" x2="221" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="226" y1="0" x2="226" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="233" y1="0" x2="233" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="243" y1="0" x2="243" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="248" y1="0" x2="248" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="259" y1="0" x2="259" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="266" y1="0" x2="266" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="277" y1="0" x2="277" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="283" y1="0" x2="283" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="288" y1="0" x2="288" y2="60"/>
<text x="145" y="77.271" text-anchor="middle" style="font-family: Tahoma; font-size: 20px; stroke: none; fill:#000000;">O-BTN.save</text></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" width="290" height="81.587" viewBox="0 0 290 81.587">
<desc></desc>
<rect x="0" y="0" width="290" height="81.587" style="fill:#ffffff;" />
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="2" y1="0" x2="2" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="7" y1="0" x2="7" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="13" y1="0" x2="13" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="23" y1="0" x2="23" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="33" y1="0" x2="33" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="40" y1="0" x2="40" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="45" y1="0" x2="45" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="52" y1="0" x2="52" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="59" y1="0" x2="59" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="67" y1="0" x2="67" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="75" y1="0" x2="75" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="80" y1="0" x2="80" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="90" y1="0" x2="90" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="97" y1="0" x2="97" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="107" y1="0" x2="107" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="111" y1="0" x2="111" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="117" y1="0" x2="117" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="128" y1="0" x2="128" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="133" y1="0" x2="133" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="140" y1="0" x2="140" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="149" y1="0" x2="149" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="155" y1="0" x2="155" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="162" y1="0" x2="162" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="171" y1="0" x2="171" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="178" y1="0" x2="178" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="189" y1="0" x2="189" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="195" y1="0" x2="195" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="199" y1="0" x2="199" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="210" y1="0" x2="210" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="215" y1="0" x2="215" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="221" y1="0" x2="221" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="225" y1="0" x2="225" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:8;" x1="234" y1="0" x2="234" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="244" y1="0" x2="244" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="253" y1="0" x2="253" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="257" y1="0" x2="257" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="266" y1="0" x2="266" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:6;" x1="277" y1="0" x2="277" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:2;" x1="283" y1="0" x2="283" y2="60"/>
<line fill="none" style="stroke:#000000; stroke-width:4;" x1="288" y1="0" x2="288" y2="60"/>
<text x="145" y="77.271" text-anchor="middle" style="font-family: Tahoma; font-size: 20px; stroke: none; fill:#000000;">O-BTN.skip</text></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,11 @@
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_inventory
from . import stock_location
from . import stock_move
from . import stock_move_line
from . import stock_quant
from . import vertical_lift_command

View File

@@ -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

View File

@@ -0,0 +1,159 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, exceptions, fields, models
class StockLocation(models.Model):
_inherit = "stock.location"
vertical_lift_location = fields.Boolean(
"Is a Vertical Lift View Location?",
default=False,
help="Check this box to use it as the view for Vertical Lift Shuttles.",
)
vertical_lift_kind = fields.Selection(
selection=[
("view", "View"),
("shuttle", "Shuttle"),
("tray", "Tray"),
("cell", "Cell"),
],
compute="_compute_vertical_lift_kind",
store=True,
)
# This is a one2one in practice, but this one is not really interesting.
# It's there only to be in the depends of 'vertical_lift_shuttle_id', which
# give the unique shuttle for any location in the tree (whether it's a
# shuttle, a tray or a cell)
inverse_vertical_lift_shuttle_ids = fields.One2many(
comodel_name="vertical.lift.shuttle", inverse_name="location_id", readonly=True
)
# compute the unique shuttle for any shuttle, tray or cell location, by
# going through the parents
vertical_lift_shuttle_id = fields.Many2one(
comodel_name="vertical.lift.shuttle",
compute="_compute_vertical_lift_shuttle_id",
store=True,
)
@api.depends(
"location_id", "location_id.vertical_lift_kind", "vertical_lift_location"
)
def _compute_vertical_lift_kind(self):
tree = {"view": "shuttle", "shuttle": "tray", "tray": "cell"}
for location in self:
if location.vertical_lift_location:
location.vertical_lift_kind = "view"
continue
kind = tree.get(location.location_id.vertical_lift_kind, False)
location.vertical_lift_kind = kind
@api.depends(
"inverse_vertical_lift_shuttle_ids", "location_id.vertical_lift_shuttle_id"
)
def _compute_vertical_lift_shuttle_id(self):
for location in self:
if location.inverse_vertical_lift_shuttle_ids:
# we have a unique constraint on the other side
assert len(location.inverse_vertical_lift_shuttle_ids) == 1
shuttle = location.inverse_vertical_lift_shuttle_ids
else:
shuttle = location.location_id.vertical_lift_shuttle_id
location.vertical_lift_shuttle_id = shuttle
def _hardware_vertical_lift_fetch_tray(self, cell_location=None):
payload = self._hardware_vertical_lift_fetch_tray_payload(cell_location)
return self.vertical_lift_shuttle_id._hardware_send_message(payload)
def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None):
"""Prepare "fetch" message to be sent to the vertical lift hardware
Private method, this is where the implementation actually happens.
Addons can add their instructions based on the hardware used for
this location.
The hardware used for a location can be found in:
``self.vertical_lift_shuttle_id.hardware``
Each addon can implement its own mechanism depending of this value
and must call ``super``.
The method must send the command to the vertical lift to fetch / open
the tray. If a ``cell_location`` is passed and if the hardware supports
a way to show a cell (such as a laser pointer), it should send this
command as well.
Useful information that could be needed for the drivers:
* Any field of `self` (name, barcode, ...) which is the current tray.
* Any field of `cell_location` (name, barcode, ...) which is the cell
in the tray.
* ``self.vertical_lift_shuttle_id`` is the current Shuttle, where we
find details about the hardware, the current mode (pick, put, ...).
* ``self.tray_type_id`` is the kind of tray.
* ``self.tray_type_id.width_per_cell`` and
``self.tray_type_id.depth_per_cell`` return the size of a cell in mm.
* ``cell_location.posx`` and ``posy`` are the coordinate from the
bottom-left of the tray.
* ``cell_location.tray_cell_center_position()`` returns the central
position of the cell in mm from the bottom-left of a tray. (distance
from left, distance from bottom). Can be used for instance for
highlighting the cell using a laser pointer.
Returns a message in bytes, that will be sent through
``VerticalLiftShuttle._hardware_send_message()``.
"""
if self.vertical_lift_shuttle_id.hardware == "simulation":
message = _("Opening tray {}.").format(self.name)
if cell_location:
from_left, from_bottom = cell_location.tray_cell_center_position()
message += _("<br/>Laser pointer on x{} y{} ({}mm, {}mm)").format(
cell_location.posx, cell_location.posy, from_left, from_bottom
)
return message.encode("utf-8")
else:
raise NotImplementedError()
def fetch_vertical_lift_tray(self, cell_location=None):
"""Send instructions to the vertical lift hardware to fetch a tray
Public method to use for:
* fetch the vertical lift tray and presenting it to the operator
(physically)
* direct the laser pointer to the cell's location if set
Depending on the hardware, the laser pointer may not be implemented.
The actual implementation of the method goes in the private method
``_hardware_vertical_lift_fetch_tray()``.
"""
self.ensure_one()
if self.vertical_lift_kind == "cell":
if cell_location:
raise ValueError(
"cell_location cannot be set when the location is a cell."
)
tray = self.location_id
tray.fetch_vertical_lift_tray(cell_location=self)
elif self.vertical_lift_kind == "tray":
self._hardware_vertical_lift_fetch_tray(cell_location=cell_location)
else:
raise exceptions.UserError(
_("Cannot fetch a vertical lift tray on location %s") % (self.name,)
)
return True
def button_fetch_vertical_lift_tray(self):
self.ensure_one()
if self.vertical_lift_kind in ("cell", "tray"):
self.fetch_vertical_lift_tray()
return True
def button_release_vertical_lift_tray(self):
self.ensure_one()
if self.vertical_lift_kind:
self.vertical_lift_shuttle_id.release_vertical_lift_tray()
return True

View File

@@ -0,0 +1,22 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
def write(self, vals):
result = super().write(vals)
if "state" in vals:
# We cannot have fields to depends on to invalidate these computed
# 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

@@ -0,0 +1,29 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class StockMoveLine(models.Model):
_inherit = "stock.move.line"
vertical_lift_skipped = fields.Boolean(
"Skipped in Vertical Lift?",
default=False,
help="If this flag is set, it means that when the move "
"was being processed in the Vertical Lift, the operator decided to "
"skip its processing.",
)
def fetch_vertical_lift_tray_source(self):
self.ensure_one()
self.location_id.fetch_vertical_lift_tray()
# We reload mainly because otherwise, it would close
# the popup. This action is provided by the OCA module
# web_ir_actions_act_view_reload
return {"type": "ir.actions.act_view_reload"}
def fetch_vertical_lift_tray_dest(self):
self.ensure_one()
self.location_dest_id.fetch_vertical_lift_tray()
return {"type": "ir.actions.act_view_reload"}

View File

@@ -0,0 +1,18 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class StockQuant(models.Model):
_inherit = "stock.quant"
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.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,50 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import _, api, exceptions, fields, models
_logger = logging.getLogger(__name__)
class VerticalLiftCommand(models.Model):
_name = "vertical.lift.command"
_order = "shuttle_id, name desc"
_description = "commands sent to the shuttle"
def _default_name(self):
return self.env["ir.sequence"].next_by_code("vertical.lift.command")
name = fields.Char(
"Name", default=lambda s: s._default_name(), required=True, index=True
)
command = fields.Char(required=True)
answer = fields.Char()
error = fields.Char()
shuttle_id = fields.Many2one("vertical.lift.shuttle", required=True)
def record_answer(self, answer):
name = self._get_key(answer)
record = self.search([("name", "=", name)], limit=1)
if not record:
_logger.error("unable to match answer to a command: %r", answer)
raise exceptions.UserError(_("Unknown record %s") % name)
record.answer = answer
record.shuttle_id._hardware_response_callback(record)
return record
def _get_key(self, answer):
key = answer.split("|")[1:2]
if key:
return key[0]
else:
return ""
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if "name" not in values:
name = self._get_key(values.get("command"))
if name:
values["name"] = name
return super().create(vals_list)

View File

@@ -0,0 +1,506 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from collections import namedtuple
from odoo import _, api, fields, models
from odoo.addons.base_sparse_field.models.fields import Serialized
_logger = logging.getLogger(__name__)
# The following methods have been copied from 'shopfloor' module (OCA/wms)
# https://github.com/OCA/wms/blob/14.0/shopfloor/models/stock_move.py#L19
# TODO: we should move them in a generic module
def split_other_move_lines(move, move_lines):
"""Substract `move_lines` from `move.move_line_ids`, put the result
in a new move and returns it.
"""
other_move_lines = move.move_line_ids - move_lines
if other_move_lines or move.state == "partially_available":
qty_to_split = move.product_uom_qty - sum(move_lines.mapped("product_uom_qty"))
backorder_move_vals = move._split(qty_to_split)
backorder_move = move.create(backorder_move_vals)
if not backorder_move:
return False
backorder_move._action_confirm(merge=False)
backorder_move.move_line_ids = other_move_lines
backorder_move._recompute_state()
backorder_move._action_assign()
move._recompute_state()
return backorder_move
return False
def extract_and_action_done(move):
"""Extract the moves in a separate transfer and validate them.
You can combine this method with `split_other_move_lines` method
to first extract some move lines in a separate move, then validate it
with this method.
"""
# Put remaining qty to process from partially available moves
# in their own move (which will be then 'confirmed')
partial_moves = move.filtered(lambda m: m.state == "partially_available")
for partial_move in partial_moves:
split_other_move_lines(partial_move, partial_move.move_line_ids)
# Process assigned moves
moves = move.filtered(lambda m: m.state == "assigned")
if not moves:
return False
for picking in moves.picking_id:
moves_todo = picking.move_lines & moves
if moves_todo == picking.move_lines:
# No need to create a new transfer if we are processing all moves
new_picking = picking
else:
new_picking = picking.copy(
{
"name": "/",
"move_lines": [],
"move_line_ids": [],
"backorder_id": picking.id,
}
)
new_picking.message_post(
body=_(
"Created from backorder "
"<a href=# data-oe-model=stock.picking data-oe-id=%d>%s</a>."
)
% (picking.id, picking.name)
)
moves_todo.write({"picking_id": new_picking.id})
moves_todo.package_level_id.write({"picking_id": new_picking.id})
moves_todo.move_line_ids.write({"picking_id": new_picking.id})
moves_todo.move_line_ids.package_level_id.write(
{"picking_id": new_picking.id}
)
new_picking.action_assign()
assert new_picking.state == "assigned"
new_picking._action_done()
return True
# /methods
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)
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)
state = fields.Selection(
selection=lambda self: self._selection_states(),
default=lambda self: self._initial_state,
)
_initial_state = None # to define in sub-classes
# if there is an action and it's returning True, the transition is done,
# otherwise not
Transition = namedtuple("Transition", "current_state next_state action direct_eval")
# default values to None
Transition.__new__.__defaults__ = (None,) * len(Transition._fields)
_sql_constraints = [
(
"shuttle_id_unique",
"UNIQUE(shuttle_id)",
"One pick can be run at a time for a shuttle.",
)
]
def _selection_states(self):
return []
def _transitions(self):
"""Define the transitions between the states
To set in sub-classes.
It is a tuple of a ``Transition`` instances, evaluated in order.
A transition has a source step, a destination step, a function and a
flag ``direct_eval``.
When the function returns True, the transition is applied, otherwise,
the next transition matching the current step is evaluated.
When a transition has no function, it is always applied.
The flag ``direct_eval`` indicates that the workflow should directly
evaluates again the transitions to reach the next step. It allows to
use "virtual" steps that will never be kept for users but be used as
router.
The initial state must be defined in the attribute ``_initial_state``.
The transition from a step to another are triggered by a call to
``next_step()``. This method is called in several places:
* ``reset_steps()`` (called when the screen opens)
* ``button_save()``, generally used to post the move
* ``button_release()``, generally used to go to the next line
* ``on_barcode_scanned()``, the calls to ``next_step()`` are to
implement in sub-classed if the scanned barcode leads to the next
step
Example of workflow described below:
::
_initial_state = "noop"
def _selection_states(self):
return [
("noop", "No operations"),
("scan_destination", "Scan New Destination Location"),
("save", "Put goods in tray and save"),
("release", "Release"),
]
def _transitions(self):
return (
self.Transition(
"noop",
"scan_destination",
lambda self: self.select_next_move_line()
),
self.Transition("scan_destination", "save"),
self.Transition("save", "release"),
self.Transition(
"release",
"scan_destination",
lambda self: self.select_next_move_line()
),
self.Transition("release", "noop"),
)
When we arrive on the screen, the ``on_screen_open`` methods resets the
steps (``reset_steps()``). It ensures the current step is ``noop`` and
directly tries to reach the next step (call to ``next_step()``).
It tries to go from ``noop`` to ``scan_destination``, calling
``self.select_next_move_line()``. If the method finds a line, it
returns True and the transition is applied, otherwise, the step stays
``noop``.
The transitions from ``scan_destination`` and ``save`` and from
``save`` and ``release`` are always applied when ``next_step()`` is
called (``scan_destination`` → ``save`` from ``on_barcode_scanned``
when a destination was found, ``save`` → ``release`` from the save
button).
When ``button_release()`` is called, it calls ``next_step()`` which
first evaluates ``self.select_next_move_line()``: if a move line remains, it
goes to ``scan_destination``, otherwise to ``noop``.
"""
return ()
def step(self):
return self.state
def next_step(self, direct_eval=False):
current_state = self.state
for transition in self._transitions():
if direct_eval and not transition.direct_eval:
continue
if transition.current_state != current_state:
continue
if not transition.action or transition.action(self):
_logger.debug(
"Transition %s%s",
transition.current_state,
transition.next_state,
)
self.state = transition.next_state
break
# reevaluate the transitions if we have a new state with direct_eval transitions
if self.state != current_state and any(
transition.direct_eval
for transition in self._transitions()
if transition.current_state == self.state
):
self.next_step(direct_eval=True)
def reset_steps(self):
if not self._initial_state:
raise NotImplementedError("_initial_state must be defined")
self.state = self._initial_state
self.next_step()
def on_barcode_scanned(self, barcode):
self.ensure_one()
# to implement in sub-classes
def on_screen_open(self):
"""Called when the screen is opened"""
self.reset_steps()
def onchange(self, values, field_name, field_onchange):
if field_name == "_barcode_scanned":
# _barcode_scanner is implemented (in the barcodes module) as an
# onchange, which is really annoying when we want it to act as a
# normal button and actually have side effect in the database
# (update line, go to the next step, ...). This override shorts the
# onchange call and calls the scanner method as a normal method.
self.on_barcode_scanned(values["_barcode_scanned"])
# We can't know which fields on_barcode_scanned changed, refresh
# everything.
return {"value": self.read()[0]}
else:
return super().onchange(values, field_name, field_onchange)
@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 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()
def process_current(self):
"""Process the action (pick, put, ...)
To implement in sub-classes
"""
raise NotImplementedError
def button_save(self):
"""Confirm the operation (set move to done, ...)"""
self.ensure_one()
if not self.step() == "save":
return
self.next_step()
def button_release(self):
"""Release the operation, go to the next"""
self.ensure_one()
if not self.step() == "release":
return
self.next_step()
def _render_product_packagings(self, product):
if not product:
return ""
return self.env["ir.qweb"]._render(
"stock_vertical_lift.packagings",
self._prepare_values_for_product_packaging(product),
)
def _prepare_values_for_product_packaging(self, product):
return {"product": product}
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"))
def _rainbow_man(self, message=None):
if not message:
message = _("Congrats, you cleared the queue!")
return {
"effect": {
"fadeout": "slow",
"message": message,
"img_url": "/web/static/src/img/smile.svg",
"type": "rainbow_man",
}
}
def _send_notification_refresh(self):
"""Send a refresh notification
Generally, you want to call the method
_send_notification_refresh() on VerticalLiftShuttle so you
don't need to know the id of the current operation.
Other notifications can be implemented, they have to be
added in static/src/js/vertical_lift.js and the message
must contain an "action" and "params".
"""
self.ensure_one()
channel = "notify_vertical_lift_screen"
bus_message = {
"action": "refresh",
"params": {"model": self._name, "id": self.id},
}
self.env["bus.bus"].sendone(channel, bus_message)
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", readonly=True
)
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=False,
)
# 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:
product = record.current_move_line_id.product_id
if not product:
record.product_packagings = ""
continue
content = self._render_product_packagings(product)
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):
record.tray_qty = 0.0
continue
product = record.current_move_line_id.product_id
location = record.tray_location_id
record.tray_qty = self._get_tray_qty(product, location)
@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 process_current(self):
line = self.current_move_line_id
if line.state in ("assigned", "partially_available"):
line.qty_done = line.product_qty
# if the move has other move lines, it is split to have only this move line
split_other_move_lines(line.move_id, line)
extract_and_action_done(line.move_id)
return True
def fetch_tray(self):
raise NotImplementedError
def reset_steps(self):
self.clear_current_move_line()
super().reset_steps()
def clear_current_move_line(self):
self.current_move_line_id = False
return True

View File

@@ -0,0 +1,268 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.tools import float_compare
from odoo.addons.base_sparse_field.models.fields import Serialized
# 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"
_initial_state = "noop"
def _selection_states(self):
return [
("noop", "No inventory in progress"),
("quantity", "Inventory, please enter the amount"),
("confirm_wrong_quantity", "The quantity does not match, are you sure?"),
# save is never visible, but save and go to the next or noop directly
("save", "Save"),
# no need for release and save button here?
# ("release", "Release"),
]
def _transitions(self):
return (
self.Transition(
"noop",
"quantity",
# transition only if inventory lines are found
lambda self: self.select_next_inventory_line(),
),
self.Transition(
"quantity",
"save",
lambda self: self._has_identical_quantity(),
),
self.Transition(
"quantity",
"confirm_wrong_quantity",
lambda self: self._start_confirm_wrong_quantity(),
),
self.Transition(
"confirm_wrong_quantity",
"save",
lambda self: self.quantity_input == self.last_quantity_input,
),
# if the confirmation of the quantity is different, cycle back to
# the 'quantity' step
self.Transition(
"confirm_wrong_quantity",
"quantity",
lambda self: self._go_back_to_quantity_input(),
),
# go to quantity if we have lines in queue, otherwise, go to noop
self.Transition(
"save",
"quantity",
lambda self: self.process_current()
and self.select_next_inventory_line(),
# when we reach 'save', this transition is directly
# evaluated
direct_eval=True,
),
self.Transition(
"save",
"noop",
lambda self: self.process_current()
and self.clear_current_inventory_line(),
# when we reach 'save', this transition is directly
# evaluated
direct_eval=True,
),
)
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()
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:
product = record.current_inventory_line_id.product_id
if not product:
record.product_packagings = ""
continue
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.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 reset_steps(self):
self.clear_current_inventory_line()
super().reset_steps()
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 _start_confirm_wrong_quantity(self):
self.last_quantity_input = self.quantity_input
self.quantity_input = 0.0
return True
def _go_back_to_quantity_input(self):
self.last_quantity_input = self.quantity_input
self.quantity_input = 0.0
return True
def clear_current_inventory_line(self):
self.write(
{
"quantity_input": 0.0,
"last_quantity_input": 0.0,
"current_inventory_line_id": False,
}
)
return True
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",
)
self.current_inventory_line_id = next_line
if next_line:
self.fetch_tray()
return bool(next_line)
def process_current(self):
line = self.current_inventory_line_id
if 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()
self.quantity_input = self.last_quantity_input = 0.0
return True
def button_save(self):
self.ensure_one()
if not self.step() in ("quantity", "confirm_wrong_quantity"):
return
self.next_step()
if self.step() == "noop":
# close the tray once everything is inventoried
self.shuttle_id.release_vertical_lift_tray()
# sorry not sorry
return self._rainbow_man()

View File

@@ -0,0 +1,122 @@
# 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"
_initial_state = "noop"
def _selection_states(self):
return [
("noop", "No operations"),
("scan_destination", "Scan New Destination Location"),
("save", "Pick goods and save"),
("release", "Release"),
("skip", "Skip"), # Virtual state.
]
def _transitions(self):
return (
self.Transition(
"noop", "scan_destination", lambda self: self.select_next_move_line()
),
self.Transition("scan_destination", "skip", lambda self: self.is_skipped()),
self.Transition("scan_destination", "save"),
self.Transition("save", "skip", lambda self: self.is_skipped()),
self.Transition("save", "release", lambda self: self.process_current()),
self.Transition("release", "skip", lambda self: self.is_skipped()),
# go to scan_destination if we have lines in queue, otherwise, go to noop
self.Transition(
"release", "scan_destination", lambda self: self.select_next_move_line()
),
self.Transition("release", "noop"),
self.Transition(
"skip",
"scan_destination",
lambda self: self.select_next_move_line(),
direct_eval=True,
),
self.Transition("skip", "noop", direct_eval=True),
)
def is_skipped(self):
"""Was the current stock.move.line marked as to be skipped?"""
self.ensure_one()
return self.current_move_line_id.vertical_lift_skipped
def on_barcode_scanned(self, barcode):
self.ensure_one()
if not self.current_move_line_id or self.current_move_line_id.state == "done":
return
if self.step() == "scan_destination":
location = self.env["stock.location"].search([("barcode", "=", barcode)])
if location:
self.location_dest_id = location
self.next_step()
else:
self.env.user.notify_warning(
_("No location found for barcode {}").format(barcode)
)
def _domain_move_lines_to_do(self):
domain = [
("state", "in", ("assigned", "partially_available")),
("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")]
)
domain = [
("state", "in", ("assigned", "partially_available")),
("location_id", "child_of", shuttle_locations.ids),
]
return domain
def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_source()
def select_next_move_line(self):
self.ensure_one()
next_move_line_order = "vertical_lift_skipped"
if self._order:
# If there already exists an order, keep it.
next_move_line_order += "," + self._order
next_move_line = self.env["stock.move.line"].search(
self._domain_move_lines_to_do(), limit=1, order=next_move_line_order
)
self.current_move_line_id = next_move_line
if next_move_line:
if next_move_line.vertical_lift_skipped:
# If a move line that was previously skipped was selected,
# we allow to process it again (maybe this time it's not
# skipped).
next_move_line.vertical_lift_skipped = False
self.fetch_tray()
return True
return False
def button_release(self):
"""Release the operation, go to the next"""
super().button_release()
if self.step() == "noop":
# we don't need to release (close) the tray until we have reached
# the last line: the release is implicit when a next line is
# fetched
self.shuttle_id.release_vertical_lift_tray()
# sorry not sorry
return self._rainbow_man()
def button_skip(self):
"""Skip the operation, go to the next"""
self.ensure_one()
self.current_move_line_id.vertical_lift_skipped = True
if self.step() != "noop":
self.next_step()

View File

@@ -0,0 +1,182 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, fields, models
from odoo.osv.expression import AND
class VerticalLiftOperationPut(models.Model):
_name = "vertical.lift.operation.put"
_inherit = "vertical.lift.operation.transfer"
_description = "Vertical Lift Operation Put"
_initial_state = "scan_source"
def _selection_states(self):
return [
("scan_source", "Scan a package, product or lot to put-away"),
("scan_tray_type", "Scan Tray Type"),
("save", "Put goods in tray and save"),
("release", "Release"),
]
def _transitions(self):
return (
self.Transition(
"scan_source",
"scan_tray_type",
# transition only if a move line has been selected
# (by on_barcode_scanner)
lambda self: self.current_move_line_id,
),
self.Transition("scan_tray_type", "save"),
self.Transition("save", "release", lambda self: self.process_current()),
self.Transition(
"release", "scan_source", lambda self: self.clear_current_move_line()
),
)
def _domain_move_lines_to_do(self):
domain = [
("state", "in", ("assigned", "partially_available")),
("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 = [
("state", "in", ("assigned", "partially_available")),
("location_dest_id", "child_of", shuttle_locations.ids),
]
return domain
def on_barcode_scanned(self, barcode):
self.ensure_one()
if self.step() == "scan_source":
self._scan_source_action(barcode)
elif self.step() in ("scan_tray_type", "save"):
# note: we must be able to scan a different tray type when we are
# in the save step too, in case we couldn't put it in the first one
# for some reason.
self._scan_tray_type_action(barcode)
def _scan_source_action(self, barcode):
line = self._find_move_line(barcode)
if line:
self.current_move_line_id = line
self.next_step()
else:
self.env.user.notify_warning(
_("No move line found for barcode {}").format(barcode)
)
def _scan_tray_type_action(self, barcode):
tray_type = self._find_tray_type(barcode)
if tray_type:
if self._assign_available_cell(tray_type):
self.fetch_tray()
if self.step() == "scan_tray_type":
# when we are in "save" step, stay here
self.next_step()
else:
self.env.user.notify_warning(
_('No free space for tray type "{}" in this shuttle.').format(
tray_type.display_name
)
)
else:
self.env.user.notify_warning(
_("No tray type found for barcode {}").format(barcode)
)
def _find_tray_type(self, barcode):
return self.env["stock.location.tray.type"].search(
[("code", "=", barcode)], limit=1
)
def _find_move_line(self, barcode):
package = self.env["stock.quant.package"].search([("name", "=", barcode)])
if package:
return self._find_move_line_for_package(package)
lot = self.env["stock.production.lot"].search([("name", "=", barcode)])
if lot:
return self._find_move_line_for_lot(package)
product = self.env["product.product"].search([("barcode", "=", barcode)])
if not product:
packaging = self.env["product.packaging"].search(
[("product_id", "!=", False), ("barcode", "=", barcode)]
)
product = packaging.product_id
if product:
return self._find_move_line_for_product(product)
def _find_move_line_for_package(self, package):
domain = AND(
[self._domain_move_lines_to_do_all(), [("package_id", "in", package.ids)]]
)
return self.env["stock.move.line"].search(domain, limit=1)
def _find_move_line_for_lot(self, lot):
domain = AND(
[
self._domain_move_lines_to_do_all(),
[
("lot_id", "=", lot.id),
# if the lot is in a package, the package must be scanned
("package_id", "=", False),
],
]
)
return self.env["stock.move.line"].search(domain, limit=1)
def _find_move_line_for_product(self, product):
domain = AND(
[
self._domain_move_lines_to_do_all(),
[
("product_id", "=", product.id),
# if the lot is in a package, the package must be scanned
("package_id", "=", False),
],
]
)
return self.env["stock.move.line"].search(domain, limit=1)
def _check_tray_type(self, barcode):
location = self.current_move_line_id.location_dest_id
tray_type = location.cell_in_tray_type_id
return barcode == tray_type.code
def _assign_available_cell(self, tray_type):
locations = self.env["stock.location"].search(
[
("id", "child_of", self.location_id.id),
("cell_in_tray_type_id", "=", tray_type.id),
]
)
location = fields.first(
locations.filtered(lambda loc: not loc.tray_cell_contains_stock)
)
if location:
self.current_move_line_id.location_dest_id = location
self.current_move_line_id.package_level_id.location_dest_id = location
return True
return False
def fetch_tray(self):
self.current_move_line_id.fetch_vertical_lift_tray_dest()
def button_release(self):
super().button_release()
if self.count_move_lines_to_do_all() == 0:
# we don't need to release (close) the tray until we have reached
# the last line: the release is implicit when a next line is
# fetched if the tray change
self.shuttle_id.release_vertical_lift_tray()
# sorry not sorry
return self._rainbow_man()

View File

@@ -0,0 +1,261 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import socket
import ssl
from odoo import _, fields, models
_logger = logging.getLogger(__name__)
class VerticalLiftShuttle(models.Model):
_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",
required=True,
)
location_id = fields.Many2one(
comodel_name="stock.location",
required=True,
domain="[('vertical_lift_kind', '=', 'shuttle')]",
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
)
server = fields.Char(help="hostname or IP address of the server")
port = fields.Integer(
help="network port of the server on which to send the message"
)
use_tls = fields.Boolean(
help="set this if the server expects TLS wrapped communication"
)
command_ids = fields.One2many(
"vertical.lift.command", "shuttle_id", string="Hardware commands"
)
_sql_constraints = [
(
"location_id_unique",
"UNIQUE(location_id)",
"You cannot have two shuttles using the same location.",
)
]
def _selection_hardware(self):
return [("simulation", "Simulation")]
@property
def _model_for_mode(self):
return {
"pick": "vertical.lift.operation.pick",
"put": "vertical.lift.operation.put",
"inventory": "vertical.lift.operation.inventory",
}
@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"
),
}
def _hardware_send_message(self, payload):
"""default implementation for message sending
If in hardware is 'simulation' then display a simple message.
Otherwise defaults to connecting to server:port using a TCP socket
(optionnally wrapped with TLS) and sending the payload.
:param payload: a bytes object containing the payload
"""
self.ensure_one()
_logger.info("send %r", payload)
command_values = {"shuttle_id": self.id, "command": payload.decode()}
self.env["vertical.lift.command"].sudo().create(command_values)
if self.hardware == "simulation":
self.env.user.notify_info(message=payload, title=_("Lift Simulation"))
return True
else:
conn = self._hardware_get_server_connection()
try:
offset = 0
while True:
size = conn.send(payload[offset:])
offset += size
if offset >= len(payload) or not size:
break
finally:
self._hardware_release_server_connection(conn)
def _hardware_response_callback(self, command):
"""should be called when a response is received from the hardware
:param response: a string
"""
success = self._check_server_response(command)
self._send_notification_refresh(success)
def _check_server_response(self, command):
"""Use this to check if the response is a success or a failure
:param payload: the payload sent
:param response: the response received
:return: True if the response is a succes, False otherwise
"""
return True
def _hardware_release_server_connection(self, conn):
conn.close()
def _hardware_get_server_connection(self):
"""This implementation will yield a new connection to the server
and close() it when exiting the context.
Override to match the communication protocol of your hardware"""
conn = socket.create_connection((self.server, self.port))
if self.use_tls:
ctx = ssl.create_default_context()
self._hardware_update_tls_context(ctx)
conntls = ctx.wrap_socket(conn, server_hostname=self.server)
return conntls
else:
return conn
def _hardware_update_tls_context(self, context):
"""Update the TLS context, e.g. to add a client certificate.
This method does nothing, override to match your communication
protocol."""
pass # noqa
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.ensure_one()
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": operation._name,
"views": [[self.env.ref(screen_xmlid).id, "form"]],
"res_id": operation.id,
"target": "fullscreen",
"flags": {
"withControlPanel": False,
"form_view_initial_mode": "edit",
"no_breadcrumbs": True,
},
}
def action_menu(self):
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,
}
def action_back_to_settings(self):
self.release_vertical_lift_tray()
action_xmlid = "stock_vertical_lift.vertical_lift_shuttle_action"
action = self.env.ref(action_xmlid).read()[0]
action["target"] = "main"
return action
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",
}
# TODO: should the mode be changed on all the shuttles at the same time?
def switch_pick(self):
self.mode = "pick"
self.release_vertical_lift_tray()
return self.action_open_screen()
def switch_put(self):
self.mode = "put"
self.release_vertical_lift_tray()
return self.action_open_screen()
def switch_inventory(self):
self.mode = "inventory"
self.release_vertical_lift_tray()
return self.action_open_screen()
def _hardware_vertical_lift_release_tray_payload(self):
"""Prepare "release" message to be sent to the vertical lift hardware
Private method, this is where the implementation actually happens.
Addons can add their instructions based on the hardware used for
this location.
The hardware used for a location can be found in:
``self.hardware``
Each addon can implement its own mechanism depending of this value
and must call ``super``.
The method must send the command to the vertical lift to release (close)
the tray.
Returns a message in bytes, that will be sent through
``VerticalLiftShuttle._hardware_send_message()``.
"""
if self.hardware == "simulation":
message = _("Releasing tray")
return message.encode("utf-8")
else:
raise NotImplementedError()
def release_vertical_lift_tray(self):
"""Send instructions to the vertical lift hardware to close trays
The actual implementation of the method goes in the private method
``_hardware_vertical_lift_release_tray()``.
"""
self.ensure_one()
payload = self._hardware_vertical_lift_release_tray_payload()
return self._hardware_send_message(payload)
def _send_notification_refresh(self, success):
"""Send a refresh notification to the current opened screen
The form controller on the front-end side will instantaneously
refresh the form with the latest committed data.
It can be used for instance after a vertical lift hardware
event occurred to inform the user on their screen.
The method is private only to prevent xml/rpc calls to
interact with the screen.
"""
# XXX do we want to do something special in the notification?
self._operation_for_mode()._send_notification_refresh()

View File

@@ -0,0 +1,55 @@
General
~~~~~~~
In Inventory Settings, you must have:
* Storage Locations
* Multi-Warehouses
* Multi-Step Routes
Locations
~~~~~~~~~
Additional configuration parameters are added in Locations:
* Sub-locations of a location with the "Is a Vertical Lift View Location"
activated are considered as "Shuttles". A shuttle is a vertical lift shelf.
* Sub-locations of shuttles are considered as "Trays", which is a tier of a
shuttle. When a tray is created, a tray type must be selected. When saved, the
tray location will automatically create as many sub-locations - called
"Cells" - as the tray type contains.
* The tray type of a tray can be changed as long as none of its cell contains
products. When changed, it archives the cells and creates new ones as
configured on the new tray type.
Tray types
~~~~~~~~~~
Tray types can be configured in the Inventory settings.
A tray type defines how much cells a tray can hold. It is a square or rectangle
matrix of n cols * m rows.
Vertical Lift Shuttles
~~~~~~~~~~~~~~~~~~~~~~
The Shuttles are the Vertical Lift Trays. One Shuttle entity has to be created
in Odoo for each physical shuttle. Depending of the subsidiary addons installed
(eg. Kardex), different options may be required (host address, ...). The base
addon only includes shuttles of kind "simulation" which will not send orders to
the hardware.
Put-away configuration
~~~~~~~~~~~~~~~~~~~~~~
If you want to use put-away in the vertical lift, the Receipts must have the
vertical lift view as destination. E.g. create put-away rules on the products
so when they arrive in WH/Stock, they are stored in WH/Stock/Vertical Lift. On
the put-away screen, when scanning the tray type to store, the destination will
be updated with an available cell of the same tray type in the current shuttle.
Barcodes
~~~~~~~~
The operations allowed in the screen for the vertical lift (save, release, skip)
can be triggered using a barcode. For this, print the barcodes contained in the
folder 'images'.

View File

@@ -0,0 +1,5 @@
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
Trobz
* Dung Tran <dungtd@trobz.com>

View File

@@ -0,0 +1,3 @@
The development of this module has been financially supported by:
* Camptocamp

View File

@@ -0,0 +1,3 @@
Add configuration and dedicated screens to work with Vertical Lift
systems (such as Kardex Remstar, Modula, ...). Drivers for controlling
the lifts physically must be added by additional addons.

View File

@@ -0,0 +1 @@
The barcodes used are of the type Code 128 (with the code set B).

View File

@@ -0,0 +1,8 @@
* 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

View File

@@ -0,0 +1,8 @@
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
access_vertical_lift_command,vertical_lift_command,model_vertical_lift_command,base.group_user,1,0,0,0
access_vertical_lift_shuttle_manual_barcode,access_vertical_lift_shuttle_manual_barcode wizard,model_vertical_lift_shuttle_manual_barcode,stock.group_stock_user,1,1,1,0
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
7 access_vertical_lift_command vertical_lift_command model_vertical_lift_command base.group_user 1 0 0 0
8 access_vertical_lift_shuttle_manual_barcode access_vertical_lift_shuttle_manual_barcode wizard model_vertical_lift_shuttle_manual_barcode stock.group_stock_user 1 1 1 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,524 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Vertical Lift</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="vertical-lift">
<h1 class="title">Vertical Lift</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_vertical_lift"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/153/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>Add configuration and dedicated screens to work with Vertical Lift
systems (such as Kardex Remstar, Modula, …). Drivers for controlling
the lifts physically must be added by additional addons.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a><ul>
<li><a class="reference internal" href="#general" id="id2">General</a></li>
<li><a class="reference internal" href="#locations" id="id3">Locations</a></li>
<li><a class="reference internal" href="#tray-types" id="id4">Tray types</a></li>
<li><a class="reference internal" href="#vertical-lift-shuttles" id="id5">Vertical Lift Shuttles</a></li>
<li><a class="reference internal" href="#put-away-configuration" id="id6">Put-away configuration</a></li>
<li><a class="reference internal" href="#barcodes" id="id7">Barcodes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#development" id="id8">Development</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id9">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id10">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id11">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id12">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id13">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id14">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id15">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<div class="section" id="general">
<h2><a class="toc-backref" href="#id2">General</a></h2>
<p>In Inventory Settings, you must have:</p>
<blockquote>
<ul class="simple">
<li>Storage Locations</li>
<li>Multi-Warehouses</li>
<li>Multi-Step Routes</li>
</ul>
</blockquote>
</div>
<div class="section" id="locations">
<h2><a class="toc-backref" href="#id3">Locations</a></h2>
<p>Additional configuration parameters are added in Locations:</p>
<ul class="simple">
<li>Sub-locations of a location with the “Is a Vertical Lift View Location”
activated are considered as “Shuttles”. A shuttle is a vertical lift shelf.</li>
<li>Sub-locations of shuttles are considered as “Trays”, which is a tier of a
shuttle. When a tray is created, a tray type must be selected. When saved, the
tray location will automatically create as many sub-locations - called
“Cells” - as the tray type contains.</li>
<li>The tray type of a tray can be changed as long as none of its cell contains
products. When changed, it archives the cells and creates new ones as
configured on the new tray type.</li>
</ul>
</div>
<div class="section" id="tray-types">
<h2><a class="toc-backref" href="#id4">Tray types</a></h2>
<p>Tray types can be configured in the Inventory settings.
A tray type defines how much cells a tray can hold. It is a square or rectangle
matrix of n cols * m rows.</p>
</div>
<div class="section" id="vertical-lift-shuttles">
<h2><a class="toc-backref" href="#id5">Vertical Lift Shuttles</a></h2>
<p>The Shuttles are the Vertical Lift Trays. One Shuttle entity has to be created
in Odoo for each physical shuttle. Depending of the subsidiary addons installed
(eg. Kardex), different options may be required (host address, …). The base
addon only includes shuttles of kind “simulation” which will not send orders to
the hardware.</p>
</div>
<div class="section" id="put-away-configuration">
<h2><a class="toc-backref" href="#id6">Put-away configuration</a></h2>
<p>If you want to use put-away in the vertical lift, the Receipts must have the
vertical lift view as destination. E.g. create put-away rules on the products
so when they arrive in WH/Stock, they are stored in WH/Stock/Vertical Lift. On
the put-away screen, when scanning the tray type to store, the destination will
be updated with an available cell of the same tray type in the current shuttle.</p>
</div>
<div class="section" id="barcodes">
<h2><a class="toc-backref" href="#id7">Barcodes</a></h2>
<p>The operations allowed in the screen for the vertical lift (save, release, skip)
can be triggered using a barcode. For this, print the barcodes contained in the
folder images.</p>
</div>
</div>
<div class="section" id="development">
<h1><a class="toc-backref" href="#id8">Development</a></h1>
<p>The barcodes used are of the type Code 128 (with the code set B).</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id9">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Complete screen workflows (currently enough for a demo, not for production)</li>
<li>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)</li>
<li>Put-away: handle packages</li>
<li>Handle “multi-shuttle” put-away</li>
<li>Create glue module for product_expiry</li>
<li>Challenge the save + release buttons and workflow</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id10">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_vertical_lift%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id11">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id12">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id13">Contributors</a></h2>
<ul class="simple">
<li>Guewen Baconnier &lt;<a class="reference external" href="mailto:guewen.baconnier&#64;camptocamp.com">guewen.baconnier&#64;camptocamp.com</a>&gt;</li>
</ul>
<p>Trobz</p>
<ul class="simple">
<li>Dung Tran &lt;<a class="reference external" href="mailto:dungtd&#64;trobz.com">dungtd&#64;trobz.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id14">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id15">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift">OCA/stock-logistics-warehouse</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,79 @@
odoo.define("stock_vertical_lift.vertical_lift", function (require) {
"use strict";
var KanbanRecord = require("web.KanbanRecord");
var FormController = require("web.FormController");
KanbanRecord.include({
_openRecord: function () {
if (
this.modelName === "vertical.lift.shuttle" &&
this.$el.hasClass("open_shuttle_screen")
) {
var self = this;
this._rpc({
method: "action_open_screen",
model: self.modelName,
args: [self.id],
}).then(function (action) {
self.trigger_up("do_action", {action: action});
});
} else {
this._super.apply(this, arguments);
}
},
});
FormController.include({
init: function () {
this._super.apply(this, arguments);
if (this.modelName.startsWith("vertical.lift.operation.")) {
this.call("bus_service", "addChannel", "notify_vertical_lift_screen");
this.call(
"bus_service",
"on",
"notification",
this,
this.vlift_bus_notification
);
this.call("bus_service", "startPolling");
}
},
vlift_bus_notification: function (notifications) {
var self = this;
_.each(notifications, function (notification) {
var channel = notification[0];
var message = notification[1];
if (channel === "notify_vertical_lift_screen") {
switch (message.action) {
case "refresh":
self.vlift_bus_action_refresh(message.params);
break;
}
}
});
},
vlift_bus_action_refresh: function (params) {
var selectedIds = this.getSelectedIds();
if (!selectedIds.length) {
return;
}
var currentId = selectedIds[0];
if (params.id === currentId && params.model === this.modelName) {
this.reload();
}
},
destroy: function () {
if (this.modelName.startsWith("vertical.lift.operation.")) {
this.call(
"bus_service",
"deleteChannel",
"notify_vertical_lift_screen"
);
}
this._super.apply(this, arguments);
},
});
return {};
});

View File

@@ -0,0 +1,135 @@
.o_web_client.o_fullscreen {
$o-shuttle-padding: $o-horizontal-padding;
.o_form_view.o_vlift_shuttle {
display: flex;
flex-flow: column nowrap;
padding: 0;
font-size: 16px;
@include media-breakpoint-up(xl) {
font-size: 18px;
}
.btn {
font-size: 1em;
padding: 1em;
margin: 0 5px;
}
.o_shuttle_header {
display: flex;
flex-flow: row wrap;
padding: $o-shuttle-padding;
}
.o_shuttle_header_content {
display: flex;
flex-flow: row nowrap;
font-size: 2em;
flex: 1 0 auto;
align-items: center;
width: 33%;
&.o_shuttle_header_right {
justify-content: flex-end;
}
}
.o_shuttle_actions {
display: flex;
flex-flow: row nowrap;
font-size: 1.2em;
padding: $o-shuttle-padding * 0.5;
}
.o_shuttle_operation {
text-align: center;
font-size: 2.5em;
padding: 0.5em;
color: #ffffff;
}
.o_shuttle_content {
display: flex;
flex-flow: row nowrap;
flex: 1 0 auto;
align-items: center;
&.o_shuttle_content_right {
justify-content: flex-end;
}
}
.o_shuttle_data {
display: flex;
flex-flow: row wrap;
padding: $o-shuttle-padding * 0.5;
.o_shuttle_data_content {
flex-flow: row nowrap;
font-size: 1.2em;
flex: 1 0 auto;
align-items: center;
width: 50%;
&.o_shuttle_tray {
display: flex;
justify-content: flex-end;
.o_group {
display: block;
}
}
.o_field_location_tray_matrix {
width: 450px;
}
}
.o_shuttle_highlight {
padding: 6px;
border-radius: 10px;
}
}
}
.o_vlift_shuttle_menu {
.btn {
margin-bottom: $o-shuttle-padding;
padding: 1em;
font-size: 2em;
text-transform: uppercase;
}
}
.o_vlift_shuttle_popup {
table tr {
line-height: 3;
font-size: 1.1em;
.o_list_record_remove {
width: 50px;
text-align: center;
}
}
.o_field_char {
padding: 1em;
font-size: 2em;
}
.btn {
padding: 1em;
font-size: 2em;
text-transform: uppercase;
}
}
footer .btn {
padding: 1em;
font-size: 2em;
text-transform: uppercase;
}
}

View File

@@ -0,0 +1,5 @@
from . import test_lift_command
from . import test_location
from . import test_inventory
from . import test_pick
from . import test_put

View File

@@ -0,0 +1,177 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _
from odoo.addons.stock_location_tray.tests import common
class VerticalLiftCase(common.LocationTrayTypeCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.shuttle = cls.env.ref(
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_1"
)
cls.product_socks = cls.env.ref("stock_vertical_lift.product_running_socks")
cls.product_recovery = cls.env.ref("stock_vertical_lift.product_recovery_socks")
cls.vertical_lift_loc = cls.env.ref(
"stock_vertical_lift.stock_location_vertical_lift"
)
cls.stock_location = cls.env.ref("stock.stock_location_stock")
cls.customers_location = cls.env.ref("stock.stock_location_customers")
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 = cls.env.ref(
"stock_vertical_lift." "stock_location_vertical_lift_demo_tray_1b"
)
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"
)
@classmethod
def _update_qty_in_location(
cls, location, product, quantity, package=None, lot=None
):
quants = cls.env["stock.quant"]._gather(
product, location, lot_id=lot, package_id=package, strict=True
)
# this method adds the quantity to the current quantity, so remove it
quantity -= sum(quants.mapped("quantity"))
cls.env["stock.quant"]._update_available_quantity(
product, location, quantity, package_id=package, lot_id=lot
)
def _open_screen(self, mode, shuttle=None):
getattr(shuttle or self.shuttle, "switch_{}".format(mode))()
# opening the screen can do some initialization for the steps
action = (shuttle or self.shuttle).action_open_screen()
return self.env[action["res_model"]].browse(action["res_id"])
@classmethod
def _create_simple_picking_out(cls, product, quantity):
stock_loc = cls.env.ref("stock.stock_location_stock")
customer_loc = cls.env.ref("stock.stock_location_customers")
picking_type = cls.env.ref("stock.picking_type_out")
partner = cls.env.ref("base.res_partner_1")
return cls.env["stock.picking"].create(
{
"picking_type_id": picking_type.id,
"partner_id": partner.id,
"location_id": stock_loc.id,
"location_dest_id": customer_loc.id,
"move_lines": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"product_uom": product.uom_id.id,
"product_uom_qty": quantity,
"picking_type_id": picking_type.id,
"location_id": stock_loc.id,
"location_dest_id": customer_loc.id,
},
)
],
}
)
@classmethod
def _create_simple_picking_in(cls, product, quantity, dest_location):
supplier_loc = cls.env.ref("stock.stock_location_suppliers")
picking_type = cls.env.ref("stock.picking_type_in")
partner = cls.env.ref("base.res_partner_1")
return cls.env["stock.picking"].create(
{
"picking_type_id": picking_type.id,
"partner_id": partner.id,
"location_id": supplier_loc.id,
"location_dest_id": dest_location.id,
"move_lines": [
(
0,
0,
{
"name": product.name,
"product_id": product.id,
"product_uom": product.uom_id.id,
"product_uom_qty": quantity,
"picking_type_id": picking_type.id,
"location_id": supplier_loc.id,
"location_dest_id": dest_location.id,
},
)
],
}
)
@classmethod
def _create_inventory(cls, products):
"""Create a draft inventory
Products is a list of tuples (bin location, product).
"""
values = {
"name": "Test Inventory",
"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 = cls.env["stock.inventory"].create(values)
inventory.action_start()
return inventory
def _test_button_release(self, move_lines, expected_state):
# for the test, we'll consider all the lines has been delivered
for move_line in move_lines:
move_line.qty_done = move_line.product_qty
move_lines.picking_id._action_done()
# release, no further operation in queue
operation = self.shuttle._operation_for_mode()
# the release button can be used only in the state... release
operation.state = "release"
result = operation.button_release()
self.assertEqual(operation.state, expected_state)
self.assertFalse(operation.current_move_line_id)
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)

View File

@@ -0,0 +1,166 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _
from .common import VerticalLiftCase
class TestInventory(VerticalLiftCase):
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(),
)
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_actions(self):
self.shuttle.switch_inventory()
action = self.shuttle.action_menu()
self.assertEqual(action["type"], "ir.actions.act_window")
self.assertEqual(action["res_model"], "vertical.lift.shuttle")
self.assertEqual(action["res_id"], self.shuttle.id)
action = self.shuttle.action_back_to_settings()
self.assertEqual(action["type"], "ir.actions.act_window")
self.assertEqual(action["res_model"], "vertical.lift.shuttle")
self.assertEqual(action["res_id"], 0)
action = self.shuttle.action_manual_barcode()
self.assertEqual(action["type"], "ir.actions.act_window")
self.assertEqual(action["res_model"], "vertical.lift.shuttle.manual.barcode")
self.assertEqual(action["name"], "Barcode")
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)])
operation = self._open_screen("inventory")
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)]
)
operation = self._open_screen("inventory")
self.assertEqual(operation.state, "quantity")
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
# noop because we have no further lines
self.assertEqual(operation.state, "noop")
self.assertFalse(operation.current_inventory_line_id)
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)]
)
operation = self._open_screen("inventory")
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)
# 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")
def test_confirm_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)]
)
operation = self._open_screen("inventory")
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)
operation.button_save()
self.assertEqual(operation.state, "quantity")
def test_inventory_next_line(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)
inventory = self._create_inventory(
[
(self.location_1a_x1y1, self.product_socks),
(self.location_1a_x2y1, self.product_recovery),
]
)
inventory_lines = inventory.line_ids
operation = self._open_screen("inventory")
operation.quantity_input = 10.0
line1 = operation.current_inventory_line_id
result = operation.button_save()
self.assertFalse(result) # no rainbow man
# go to next line
remaining_line = inventory_lines - line1
self.assertEqual(operation.state, "quantity")
self.assertEqual(operation.current_inventory_line_id, remaining_line)
self.assertEqual(operation.last_quantity_input, 0.0)
self.assertEqual(operation.quantity_input, 0.0)
def test_inventory_locations(self):
self.shuttle.switch_inventory()
opr_inventory = self.shuttle._operation_for_mode()
opr_inventory._compute_tray_data()
opr_inventory._compute_product_packagings()
self.assertEqual(opr_inventory.product_packagings, "")
opr_inventory._compute_tray_qty()
self.assertEqual(opr_inventory.tray_qty, 0.0)
self._update_qty_in_location(self.location_1a_x1y1, self.product_socks, 10)
self._create_inventory([(self.location_1a_x1y1, self.product_socks)])
self._open_screen("inventory")
opr_inventory._compute_product_packagings()
opr_inventory._compute_tray_qty()
self.assertEqual(opr_inventory.tray_qty, 10)

View File

@@ -0,0 +1,25 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import exceptions
from odoo.tools import mute_logger
from .common import VerticalLiftCase
class TestLiftCommand(VerticalLiftCase):
def test_lift_commands(self):
self.shuttle.switch_inventory()
command_id = self.shuttle.command_ids[0]
message = "Unknown record"
method_name = "odoo.addons.stock_vertical_lift.models.vertical_lift_command"
with mute_logger(method_name):
with self.assertRaisesRegex(exceptions.UserError, message):
command_id.record_answer("0|test|1")
command_id.record_answer("0|{}|1".format(command_id.name))
self.shuttle.command_ids.create(
{
"shuttle_id": self.shuttle.id,
"command": "0|test|1",
}
)

View File

@@ -0,0 +1,52 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import exceptions
from .common import VerticalLiftCase
class TestVerticalLiftLocation(VerticalLiftCase):
def test_vertical_lift_kind(self):
# this boolean is what defines a "Vertical Lift View", the upper level
# of the tree (View -> Shuttles -> Trays -> Cells)
self.assertTrue(self.vertical_lift_loc.vertical_lift_location)
self.assertEqual(self.vertical_lift_loc.vertical_lift_kind, "view")
# check types accross the hierarchy
shuttles = self.vertical_lift_loc.child_ids
self.assertTrue(
all(location.vertical_lift_kind == "shuttle" for location in shuttles)
)
trays = shuttles.mapped("child_ids")
self.assertTrue(
all(location.vertical_lift_kind == "tray" for location in trays)
)
cells = trays.mapped("child_ids")
self.assertTrue(
all(location.vertical_lift_kind == "cell" for location in cells)
)
def test_fetch_vertical_lift_tray(self):
shuttles = self.vertical_lift_loc.child_ids
trays = shuttles.mapped("child_ids")
cells = trays.mapped("child_ids")
self.assertTrue(cells[0].button_fetch_vertical_lift_tray())
message = "cell_location cannot be set when the location is a cell."
with self.assertRaisesRegex(ValueError, message):
cells[0].fetch_vertical_lift_tray(cells[0])
message = "Cannot fetch a vertical lift tray on location"
with self.assertRaisesRegex(exceptions.UserError, message):
shuttles[0].fetch_vertical_lift_tray(cells[0])
self.assertTrue(cells[0].button_release_vertical_lift_tray())
def test_create_shuttle(self):
# any location created directly under the view is a shuttle
shuttle_loc = self.env["stock.location"].create(
{
"name": "Shuttle 42",
"location_id": self.vertical_lift_loc.id,
"usage": "internal",
}
)
self.assertEqual(shuttle_loc.vertical_lift_kind, "shuttle")

View File

@@ -0,0 +1,248 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from .common import VerticalLiftCase
class TestPick(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[0]
def test_switch_pick(self):
self.shuttle.switch_pick()
self.assertEqual(self.shuttle.mode, "pick")
self.assertEqual(
self.shuttle._operation_for_mode().current_move_line_id, self.out_move_line
)
def test_pick_action_open_screen(self):
self.shuttle.switch_pick()
action = self.shuttle.action_open_screen()
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):
operation = self._open_screen("pick")
operation.select_next_move_line()
self.assertEqual(operation.current_move_line_id, self.out_move_line)
self.assertEqual(operation.state, "scan_destination")
def test_pick_select_next_move_line_was_skipped(self):
"""Previously skipped moves can be reprocessed"""
self.picking_out.move_line_ids.write({"vertical_lift_skipped": True})
operation = self._open_screen("pick")
operation.select_next_move_line()
self.assertEqual(operation.current_move_line_id, self.out_move_line)
self.assertEqual(operation.state, "scan_destination")
self.assertFalse(operation.current_move_line_id.vertical_lift_skipped)
def test_pick_save(self):
operation = self._open_screen("pick")
# assume we already scanned the destination, current state is save
operation.state = "save"
operation.current_move_line_id = self.out_move_line
operation.button_save()
self.assertEqual(operation.current_move_line_id.state, "done")
self.assertEqual(operation.state, "release")
def test_pick_skip_from_scan_destination(self):
"""Being in state Scan Destination, skip it"""
self._test_pick_skip_from_state("scan_destination")
def test_pick_skip_from_save(self):
"""Being in state Save, skip it"""
self._test_pick_skip_from_state("save")
def test_pick_skip_from_release(self):
"""Being in state Release, skip it"""
self._test_pick_skip_from_state("release")
def _test_pick_skip_from_state(self, state):
operation = self._open_screen("pick")
operation.state = state
operation.current_move_line_id = self.out_move_line
first_move_line = operation.current_move_line_id
self.assertFalse(operation.current_move_line_id.vertical_lift_skipped)
operation.button_skip()
second_move_line = operation.current_move_line_id
self.assertNotEqual(first_move_line, second_move_line)
self.assertFalse(operation.current_move_line_id.vertical_lift_skipped)
self.assertEqual(operation.current_move_line_id.state, "assigned")
self.assertEqual(operation.state, "scan_destination")
def test_pick_related_fields(self):
operation = self._open_screen("pick")
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(operation.tray_location_id, ml.location_id)
self.assertEqual(
operation.tray_name,
# parent = tray
ml.location_id.location_id.name,
)
self.assertEqual(
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(
operation.tray_type_code, ml.location_id.location_id.tray_type_id.code
)
self.assertEqual(operation.tray_x, ml.location_id.posx)
self.assertEqual(operation.tray_y, ml.location_id.posy)
# Move line related fields
self.assertEqual(operation.picking_id, ml.picking_id)
self.assertEqual(operation.picking_origin, ml.picking_id.origin)
self.assertEqual(operation.picking_partner_id, ml.picking_id.partner_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")
# cancel the picking from demo data to start from a clean state
picking_1 = self.env.ref(
"stock_vertical_lift.stock_picking_out_demo_vertical_lift_1"
)
# If stock_picking_cancel_confirm is installed, we need to explicitly
# confirm the cancellation.
try:
picking_1.cancel_confirm = True
except AttributeError:
pass
picking_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"
)
self._update_quantity_in_cell(cell1, product1, 50)
cell2 = self.env.ref(
"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._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product1, 1)
pickings |= self._create_simple_picking_out(product2, 20)
pickings |= self._create_simple_picking_out(product2, 30)
# this one should be 'assigned', so should be included in the operation
# count
unassigned = self._create_simple_picking_out(product2, 1)
pickings |= unassigned
pickings.action_confirm()
# product1 will be taken from the shuttle1, product2 from shuttle2
pickings.action_assign()
shuttle1 = self.shuttle
operation1 = shuttle1._operation_for_mode()
shuttle2 = self.env.ref(
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_2"
)
operation2 = shuttle2._operation_for_mode()
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.
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(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)
def test_on_barcode_scanned(self):
operation = self._open_screen("pick")
self.assertEqual(operation.state, "scan_destination")
# Scan wrong one first for test coverage
operation.on_barcode_scanned("test")
move_line = operation.current_move_line_id
current_destination = move_line.location_dest_id
stock_location = self.env.ref("stock.stock_location_stock")
self.assertEqual(
current_destination, self.env.ref("stock.stock_location_customers")
)
operation.on_barcode_scanned(stock_location.barcode)
self.assertEqual(move_line.location_dest_id, stock_location)
self.assertEqual(operation.state, "save")
# Done for test coverage
operation.button_save()
operation.on_barcode_scanned("test")
def test_button_release(self):
self._open_screen("pick")
self._test_button_release(self.picking_out.move_line_ids, "noop")
def test_process_current_pick(self):
operation = self._open_screen("pick")
operation.current_move_line_id = self.out_move_line
qty_to_process = self.out_move_line.product_qty
operation.process_current()
self.assertEqual(self.out_move_line.state, "done")
self.assertEqual(self.out_move_line.qty_done, qty_to_process)
def test_matrix(self):
operation = self._open_screen("pick")
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(
operation.tray_matrix,
{
"selected": [expected_x, expected_y],
# fmt: off
'cells': [
[0, 0, 0, 0],
[1, 1, 1, 0],
]
# fmt: on
},
)
def test_tray_qty(self):
cell = self.env.ref(
"stock_vertical_lift." "stock_location_vertical_lift_demo_tray_1a_x3y2"
)
self.out_move_line.location_id = cell
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(operation.tray_qty, 50)
self._update_quantity_in_cell(cell, self.out_move_line.product_id, -20)
self.assertEqual(operation.tray_qty, 30)

View File

@@ -0,0 +1,160 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from .common import VerticalLiftCase
class TestPut(VerticalLiftCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.picking_in = cls.env.ref(
"stock_vertical_lift.stock_picking_in_demo_vertical_lift_1"
)
cls.picking_in.action_confirm()
cls.in_move_line = cls.picking_in.move_line_ids
cls.in_move_line.location_dest_id = cls.shuttle.location_id
def test_put_action_open_screen(self):
self.shuttle.switch_put()
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.put")
self.assertEqual(action["res_id"], operation.id)
def test_switch_put(self):
self.shuttle.switch_put()
self.assertEqual(self.shuttle.mode, "put")
self.assertEqual(
self.shuttle._operation_for_mode().current_move_line_id,
self.env["stock.move.line"].browse(),
)
def test_put_count_move_lines(self):
# If stock_picking_cancel_confirm is installed, we need to explicitly
# confirm the cancellation.
try:
self.picking_in.cancel_confirm = True
except AttributeError:
pass
self.picking_in.action_cancel()
put1 = self._create_simple_picking_in(
self.product_socks, 10, self.location_1a_x1y1
)
put1.action_confirm()
put2 = self._create_simple_picking_in(
self.product_recovery, 10, self.vertical_lift_loc
)
put2.action_confirm()
put3 = self._create_simple_picking_in(
self.product_recovery, 10, self.vertical_lift_loc
)
put3.action_confirm()
operation = self._open_screen("put")
shuttle2 = self.env.ref(
"stock_vertical_lift.stock_vertical_lift_demo_shuttle_2"
)
operation2 = self._open_screen("put", shuttle=shuttle2)
# we don't really care about the "number_of_ops" for the
# put-away, as the move lines are supposed to have the whole
# whole shuttle view as destination
self.assertEqual(operation.number_of_ops, 1)
self.assertEqual(operation.number_of_ops_all, 3)
self.assertEqual(operation2.number_of_ops, 0)
self.assertEqual(operation2.number_of_ops_all, 3)
def test_transition_start(self):
operation = self._open_screen("put")
# we begin with an empty screen, user has to scan a package, product,
# or lot
self.assertEqual(operation.state, "scan_source")
def test_transition_scan_source_to_scan_tray_type(self):
operation = self._open_screen("put")
self.assertEqual(operation.state, "scan_source")
# wrong barcode, nothing happens
operation.on_barcode_scanned("foo")
self.assertEqual(operation.state, "scan_source")
# product scanned, move to next step
operation.on_barcode_scanned(self.product_socks.barcode)
self.assertEqual(operation.state, "scan_tray_type")
self.assertEqual(operation.current_move_line_id, self.in_move_line)
def test_transition_scan_tray_type_to_save(self):
operation = self._open_screen("put")
# assume we already scanned the product
operation.state = "scan_tray_type"
operation.current_move_line_id = self.in_move_line
# wrong barcode, nothing happens
operation.on_barcode_scanned("foo")
# tray type scanned, move to next step
operation.on_barcode_scanned(self.location_1a.tray_type_id.code)
self.assertEqual(operation.state, "save")
# a cell has been set
self.assertTrue(
self.in_move_line.location_dest_id in self.location_1a.child_ids
)
def test_change_tray_type_on_save(self):
operation = self._open_screen("put")
move_line = self.in_move_line
# assume we already scanned the product and the tray type
# and the assigned location was location_1a_x1y1
operation.current_move_line_id = move_line
move_line.location_dest_id = self.location_1a_x1y1
operation.state = "save"
# we want to use another tray with a different type though,
# so we scan again
operation.on_barcode_scanned(self.location_1b.tray_type_id.code)
self.assertTrue(
self.in_move_line.location_dest_id
in self.shuttle.location_id.child_ids.child_ids
)
# we are still in save
self.assertEqual(operation.state, "save")
# a cell has been set in the other tray
self.assertTrue(move_line.location_dest_id in self.location_1b.child_ids)
def test_transition_scan_tray_type_no_empty_cell(self):
operation = self._open_screen("put")
# assume we already scanned the product
operation.state = "scan_tray_type"
operation.current_move_line_id = self.in_move_line
# create a tray type without location, which is the same as if all the
# locations of a tray type were full
new_tray_type = self.env["stock.location.tray.type"].create(
{"name": "new tray type", "code": "test", "rows": 1, "cols": 1}
)
operation.on_barcode_scanned(new_tray_type.code)
# should stay the same state
self.assertEqual(operation.state, "scan_tray_type")
# destination not changed
self.assertEqual(self.in_move_line.location_dest_id, self.shuttle.location_id)
def test_transition_save(self):
operation = self._open_screen("put")
# first steps of the workflow are done
operation.current_move_line_id = self.in_move_line
operation.current_move_line_id.location_dest_id = self.location_1a_x1y1
operation.state = "save"
qty_to_process = self.in_move_line.product_qty
operation.button_save()
self.assertEqual(self.in_move_line.state, "done")
self.assertEqual(self.in_move_line.qty_done, qty_to_process)
def test_transition_button_release(self):
operation = self._open_screen("put")
move_line = self.in_move_line
# first steps of the workflow are done
operation.current_move_line_id = move_line
operation.current_move_line_id.location_dest_id = self.location_1a_x1y1
# for the test, we'll consider our last line has been delivered
move_line.qty_done = move_line.product_qty
move_line.move_id._action_done()
operation = self._open_screen("put")
operation.button_release()
self.assertEqual(operation.state, "scan_source")
self.assertFalse(operation.current_move_line_id)

View File

@@ -0,0 +1,23 @@
<odoo>
<template id="packagings">
<ul class="o_vlift_packaging list-unstyled">
<t t-foreach="product.packaging_ids" t-as="packaging">
<li>
<span>1</span>
<span
class="packaging_name"
itemprop="name"
t-esc="packaging.name"
/>
<span>: </span>
<span class="packaging_qty" itemprop="qty" t-esc="packaging.qty" />
<span
class="packaging_unit"
itemprop="unit"
t-esc="product.uom_id.name"
/>
</li>
</t>
</ul>
</template>
</odoo>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_location_form" model="ir.ui.view">
<field name="name">stock.location.form.vertical.lift</field>
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock_location_tray.view_location_form" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button
name="button_fetch_vertical_lift_tray"
string="Fetch Shuttle Tray"
type="object"
groups="stock.group_stock_manager"
class="oe_stat_button"
icon="fa-hand-paper-o"
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('tray', 'cell'))]}"
/>
<button
name="button_release_vertical_lift_tray"
string="Release Shuttle Tray"
type="object"
groups="stock.group_stock_manager"
class="oe_stat_button"
icon="fa-hand-rock-o"
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('tray', 'cell'))]}"
/>
</div>
<field name="return_location" position="after">
<field
name="vertical_lift_location"
attrs="{'invisible': [('vertical_lift_kind', '!=', False), ('vertical_lift_kind', '!=', 'view')]}"
/>
<field name="vertical_lift_kind" />
<field
name="vertical_lift_shuttle_id"
attrs="{'invisible': [('vertical_lift_kind', 'not in', ('shuttle', 'tray', 'cell'))]}"
/>
</field>
<field name="tray_type_id" position="attributes">
<attribute name="attrs">
{'invisible': [('cell_in_tray_type_id', '!=', False)],
'required': [('vertical_lift_kind', '=', 'tray')]}
</attribute>
</field>
</field>
</record>
<record id="view_location_search" model="ir.ui.view">
<field name="name">stock.location.search.vertical.lift</field>
<field name="model">stock.location</field>
<field name="inherit_id" ref="stock_location_tray.view_location_search" />
<field name="arch" type="xml">
<field name="tray_type_id" position="after">
<field name="vertical_lift_kind" />
<field name="vertical_lift_shuttle_id" />
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_move_line_operation_tree" model="ir.ui.view">
<field name="name">stock.move.line.operations.tree.vertical.lift</field>
<field name="model">stock.move.line</field>
<field
name="inherit_id"
ref="stock_location_tray.view_stock_move_line_operation_tree"
/>
<field name="arch" type="xml">
<button name="action_show_source_tray" position="before">
<button
name="fetch_vertical_lift_tray_source"
string="Fetch Source Tray"
type="object"
icon="fa-hand-paper-o"
attrs="{'invisible': [('tray_source_matrix', '=', {})]}"
invisible="not context.get('show_source_location')"
groups="stock.group_stock_multi_locations"
/>
</button>
<button name="action_show_dest_tray" position="before">
<button
name="fetch_vertical_lift_tray_dest"
string="Fetch Destination Tray"
type="object"
icon="fa-hand-paper-o"
attrs="{'invisible': [('tray_dest_matrix', '=', {})]}"
invisible="not context.get('show_destination_location')"
groups="stock.group_stock_multi_locations"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template
id="stock_vertical_lift_assets"
name="stock.vertical.lift.assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<link
rel="stylesheet"
type="text/scss"
href="/stock_vertical_lift/static/src/scss/vertical_lift.scss"
/>
<script
type="text/javascript"
src="/stock_vertical_lift/static/src/js/vertical_lift.js"
/>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--
When we refresh the page on the browser when we are using the
"screen" view, odoo loses the information that we want the view
to be headless, fullscreen, etc. so it's displayed pretty badly.
This view is a work-around: its priority is lower, so it will be
picked up by default on loading, and a button allows to re-open
the screen view with the proper options.
-->
<record id="vertical_lift_operation_base_button_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.base.button.view</field>
<field name="model">vertical.lift.operation.base</field>
<field name="arch" type="xml">
<form string="Operations" delete="0" create="0">
<button
string="Reopen Screen"
name="action_open_screen"
type="object"
class="btn-primary"
/>
</form>
</field>
</record>
<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">
<label for="number_of_ops" />
<field name="number_of_ops" readonly="1" />
<span>/</span>
<field name="number_of_ops_all" readonly="1" />
</div>
</div>
<div class="o_shuttle_actions">
<div class="o_shuttle_content o_shuttle_content_left">
<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"
attrs="{'invisible': [('state', '!=', 'save')]}"
/>
<!-- will react on barcode 'O-BTN.release -->
<button
name="button_release"
type="object"
string="Release"
class="btn-primary"
barcode_trigger="release"
attrs="{'invisible': [('state', '!=', 'release')]}"
/>
</div>
</div>
</div>
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
<div class="container">
<field name="state" invisible="0" readonly="True" />
</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_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>
<label for="location_dest_id" />
<div>
<field
name="location_dest_id"
class="bg-primary o_shuttle_highlight"
readonly="1"
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"
attrs="{'invisible': [('tray_type_id', '=', False)]}"
>
<group col="1">
<field name="tray_type_id" invisible="1" />
<field name="tray_name" />
<field name="tray_type_code" />
<field name="tray_x" />
<field name="tray_y" />
<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,109 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="vertical_lift_operation_inventory_button_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.inventory.button.view</field>
<field name="model">vertical.lift.operation.inventory</field>
<field name="inherit_id" ref="vertical_lift_operation_base_button_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Inventory Screen</attribute>
</form>
</field>
</record>
<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="priority">100</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Inventory Screen</attribute>
</form>
<form position="inside">
<field name="state" invisible="1" />
</form>
<button name="button_save" position="attributes">
<attribute
name="attrs"
>{'invisible': [('state', 'not in', ('quantity', 'confirm_wrong_quantity'))]}</attribute>
</button>
<button name="button_release" position="attributes">
<attribute name="invisible">1</attribute>
</button>
<xpath expr="//div[hasclass('o_shuttle_data')]" position="attributes">
<attribute
name="attrs"
>{'invisible': [('current_inventory_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_inventory_line_id" invisible="1" />
<div>
<div>
<field
name="inventory_id"
options="{'no_open': True}"
class="mr8"
/>
</div>
</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" />
<field name="package_id" />
<label
for="quantity_input"
string="Quantity"
class="ml32"
/>
<div colspan="2" class="ml32">
<h1 class="bg-primary o_shuttle_highlight">
<field
name="quantity_input"
default_focus="1"
class="oe_inline"
attrs="{'readonly': [('state', 'not in', ('quantity', 'confirm_wrong_quantity'))]}"
/>
<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_x" />
<field name="tray_y" />
</group>
<group>
<div>
<field name="tray_matrix" widget="location_tray_matrix" />
</div>
</group>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="vertical_lift_operation_pick_button_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.pick.button.view</field>
<field name="model">vertical.lift.operation.pick</field>
<field name="inherit_id" ref="vertical_lift_operation_base_button_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Pick Screen</attribute>
</form>
</field>
</record>
<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="priority">100</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Pick Screen</attribute>
</form>
<button name="button_save" position="before">
<button
name="button_skip"
type="object"
string="Skip"
icon="fa-forward"
class="btn-danger"
barcode_trigger="skip"
attrs="{'invisible': [('state', '=', 'noop')]}"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="vertical_lift_operation_put_button_view" model="ir.ui.view">
<field name="name">vertical.lift.operation.put.button.view</field>
<field name="model">vertical.lift.operation.put</field>
<field name="inherit_id" ref="vertical_lift_operation_base_button_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Put-Away Screen</attribute>
</form>
</field>
</record>
<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="priority">100</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<form position="attributes">
<attribute name="string">Put-Away Screen</attribute>
</form>
<xpath
expr="//div[hasclass('o_shuttle_header_right')]/field[@name='number_of_ops']"
position="attributes"
>
<attribute name="invisible">1</attribute>
</xpath>
<xpath
expr="//div[hasclass('o_shuttle_header_right')]/field[@name='number_of_ops']/following-sibling::span"
position="replace"
>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<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>
<field name="priority">100</field>
<field name="arch" type="xml">
<form string="Shuttle">
<div class="row o_vlift_shuttle_menu">
<div class="col-6">
<button
name="switch_pick"
type="object"
string="Pick"
class="btn-primary btn-block btn btn-lg"
/>
<button
name="switch_put"
type="object"
string="Put"
class="btn-primary btn-block btn btn-lg"
/>
<button
name="switch_inventory"
type="object"
string="Inventory"
class="btn-primary btn-block btn btn-lg"
/>
</div>
<div class="col-6">
<button
name="action_back_to_settings"
type="object"
string="Back to settings"
class="btn-danger btn-block btn-lg"
/>
</div>
</div>
<footer />
</form>
</field>
</record>
<record id="vertical_lift_shuttle_manual_barcode_form" model="ir.ui.view">
<field name="name">vertical.lift.shuttle.manual.barcode.view.form</field>
<field name="model">vertical.lift.shuttle.manual.barcode</field>
<field name="arch" type="xml">
<form string="Shuttle">
<div class="row o_vlift_shuttle_popup">
<div class="col-8">
<field name="barcode" />
</div>
<div class="col-2">
<button
name="button_save"
string="Confirm"
type="object"
class="btn-primary"
/>
</div>
</div>
<footer />
</form>
</field>
</record>
<record id="vertical_lift_shuttle_view_form" model="ir.ui.view">
<field name="name">vertical.lift.shuttle.view.form</field>
<field name="model">vertical.lift.shuttle</field>
<field name="arch" type="xml">
<form string="Shuttle">
<header>
<button
name="release_vertical_lift_tray"
string="Release tray"
type="object"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
</div>
<div class="oe_title">
<label class="oe_edit_only" for="name" />
<h1>
<field name="name" placeholder="Shuttle Name" />
</h1>
</div>
<group name="main">
<group name="left">
<field name="mode" />
<field name="location_id" />
<field name="hardware" />
</group>
<group string="Network" name="network">
<field name="server" />
<field name="port" />
<field name="use_tls" />
</group>
</group>
<group groups="base.group_no_one">
<label for="command_ids" />
<field name="command_ids">
<tree>
<field name="name" />
<field name="command" />
<field name="answer" />
<field name="error" />
<field name="create_date" />
</tree>
</field>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="vertical_lift_shuttle_view_kanban">
<field name="name">vertical.lift.shuttle.kanban</field>
<field name="model">vertical.lift.shuttle</field>
<field name="arch" type="xml">
<kanban class="o_kanban_dashboard">
<field name="name" />
<field name="mode" />
<!-- <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"
>
<div class="o_vlift_kanban_main">
<div class="o_kanban_card_content">
<div class="o_kanban_primary_left">
<div class="o_primary">
<strong class="o_kanban_record_title">
<span>
<t t-esc="record.name.value" />
</span>
</strong>
</div>
<div class="row">
<div class="col-8">
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>
</div>
</div>
<div
class="o_kanban_card_manage_pane dropdown-menu"
groups="stock.group_stock_manager"
role="menu"
>
<div
class="o_kanban_card_manage_section o_kanban_manage_reports"
>
<div role="menuitem">
<a type="edit">Settings</a>
</div>
</div>
</div>
<a
class="o_kanban_manage_toggle_button o_left"
href="#"
groups="stock.group_stock_manager"
>
<i
class="fa fa-ellipsis-v"
role="img"
aria-label="Manage"
title="Manage"
/>
</a>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record model="ir.ui.view" id="vertical_lift_shuttle_view_tree">
<field name="name">vertical.lift.shuttle.tree</field>
<field name="model">vertical.lift.shuttle</field>
<field name="arch" type="xml">
<tree string="Shuttle">
<field name="name" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="vertical_lift_shuttle_action">
<field name="name">Vertical Lift Shuttles</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">vertical.lift.shuttle</field>
<field name="view_mode">kanban,tree,form</field>
<field name="target">current</field>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Open the Shuttle Interface.
</p>
</field>
</record>
<menuitem
id="vertical_lift_shuttle"
name="Vertical Lift Shuttles"
action="vertical_lift_shuttle_action"
parent="stock.menu_stock_warehouse_mgmt"
sequence="15"
/>
</odoo>

View File

@@ -0,0 +1 @@
from . import vertical_lift_shuttle_manual_barcode

View File

@@ -0,0 +1,20 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class VerticalLiftShuttleManualBarcode(models.TransientModel):
_name = "vertical.lift.shuttle.manual.barcode"
_description = "Action to input a barcode"
barcode = fields.Char(string="Barcode")
def button_save(self):
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:
record.on_barcode_scanned(self.barcode)