mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
1
setup/stock_vertical_lift/odoo/addons/stock_vertical_lift
Symbolic link
1
setup/stock_vertical_lift/odoo/addons/stock_vertical_lift
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../stock_vertical_lift
|
||||
6
setup/stock_vertical_lift/setup.py
Normal file
6
setup/stock_vertical_lift/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
167
stock_vertical_lift/README.rst
Normal file
167
stock_vertical_lift/README.rst
Normal 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.
|
||||
3
stock_vertical_lift/__init__.py
Normal file
3
stock_vertical_lift/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
from . import wizards
|
||||
41
stock_vertical_lift/__manifest__.py
Normal file
41
stock_vertical_lift/__manifest__.py
Normal 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",
|
||||
}
|
||||
1
stock_vertical_lift/controllers/__init__.py
Normal file
1
stock_vertical_lift/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import main
|
||||
22
stock_vertical_lift/controllers/main.py
Normal file
22
stock_vertical_lift/controllers/main.py
Normal 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()
|
||||
10
stock_vertical_lift/data/ir_sequence.xml
Normal file
10
stock_vertical_lift/data/ir_sequence.xml
Normal 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>
|
||||
29
stock_vertical_lift/demo/product_demo.xml
Normal file
29
stock_vertical_lift/demo/product_demo.xml
Normal 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>
|
||||
50
stock_vertical_lift/demo/stock_inventory_demo.xml
Normal file
50
stock_vertical_lift/demo/stock_inventory_demo.xml
Normal 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>
|
||||
131
stock_vertical_lift/demo/stock_location_demo.xml
Normal file
131
stock_vertical_lift/demo/stock_location_demo.xml
Normal 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>
|
||||
63
stock_vertical_lift/demo/stock_picking_demo.xml
Normal file
63
stock_vertical_lift/demo/stock_picking_demo.xml
Normal 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>
|
||||
18
stock_vertical_lift/demo/vertical_lift_shuttle_demo.xml
Normal file
18
stock_vertical_lift/demo/vertical_lift_shuttle_demo.xml
Normal 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>
|
||||
818
stock_vertical_lift/i18n/stock_vertical_lift.pot
Normal file
818
stock_vertical_lift/i18n/stock_vertical_lift.pot
Normal 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 ""
|
||||
55
stock_vertical_lift/images/O-BTN.release.svg
Normal file
55
stock_vertical_lift/images/O-BTN.release.svg
Normal 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 |
46
stock_vertical_lift/images/O-BTN.save.svg
Normal file
46
stock_vertical_lift/images/O-BTN.save.svg
Normal 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 |
46
stock_vertical_lift/images/O-BTN.skip.svg
Normal file
46
stock_vertical_lift/images/O-BTN.skip.svg
Normal 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 |
11
stock_vertical_lift/models/__init__.py
Normal file
11
stock_vertical_lift/models/__init__.py
Normal 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
|
||||
27
stock_vertical_lift/models/stock_inventory.py
Normal file
27
stock_vertical_lift/models/stock_inventory.py
Normal 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
|
||||
159
stock_vertical_lift/models/stock_location.py
Normal file
159
stock_vertical_lift/models/stock_location.py
Normal 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
|
||||
22
stock_vertical_lift/models/stock_move.py
Normal file
22
stock_vertical_lift/models/stock_move.py
Normal 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
|
||||
29
stock_vertical_lift/models/stock_move_line.py
Normal file
29
stock_vertical_lift/models/stock_move_line.py
Normal 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"}
|
||||
18
stock_vertical_lift/models/stock_quant.py
Normal file
18
stock_vertical_lift/models/stock_quant.py
Normal 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
|
||||
50
stock_vertical_lift/models/vertical_lift_command.py
Normal file
50
stock_vertical_lift/models/vertical_lift_command.py
Normal 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)
|
||||
506
stock_vertical_lift/models/vertical_lift_operation_base.py
Normal file
506
stock_vertical_lift/models/vertical_lift_operation_base.py
Normal 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
|
||||
268
stock_vertical_lift/models/vertical_lift_operation_inventory.py
Normal file
268
stock_vertical_lift/models/vertical_lift_operation_inventory.py
Normal 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()
|
||||
122
stock_vertical_lift/models/vertical_lift_operation_pick.py
Normal file
122
stock_vertical_lift/models/vertical_lift_operation_pick.py
Normal 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()
|
||||
182
stock_vertical_lift/models/vertical_lift_operation_put.py
Normal file
182
stock_vertical_lift/models/vertical_lift_operation_put.py
Normal 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()
|
||||
261
stock_vertical_lift/models/vertical_lift_shuttle.py
Normal file
261
stock_vertical_lift/models/vertical_lift_shuttle.py
Normal 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()
|
||||
55
stock_vertical_lift/readme/CONFIGURE.rst
Normal file
55
stock_vertical_lift/readme/CONFIGURE.rst
Normal 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'.
|
||||
5
stock_vertical_lift/readme/CONTRIBUTORS.rst
Normal file
5
stock_vertical_lift/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
|
||||
Trobz
|
||||
|
||||
* Dung Tran <dungtd@trobz.com>
|
||||
3
stock_vertical_lift/readme/CREDITS.rst
Normal file
3
stock_vertical_lift/readme/CREDITS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
The development of this module has been financially supported by:
|
||||
|
||||
* Camptocamp
|
||||
3
stock_vertical_lift/readme/DESCRIPTION.rst
Normal file
3
stock_vertical_lift/readme/DESCRIPTION.rst
Normal 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.
|
||||
1
stock_vertical_lift/readme/DEVELOP.rst
Normal file
1
stock_vertical_lift/readme/DEVELOP.rst
Normal file
@@ -0,0 +1 @@
|
||||
The barcodes used are of the type Code 128 (with the code set B).
|
||||
8
stock_vertical_lift/readme/ROADMAP.rst
Normal file
8
stock_vertical_lift/readme/ROADMAP.rst
Normal 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
|
||||
8
stock_vertical_lift/security/ir.model.access.csv
Normal file
8
stock_vertical_lift/security/ir.model.access.csv
Normal 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
|
||||
|
BIN
stock_vertical_lift/static/description/icon.png
Normal file
BIN
stock_vertical_lift/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
524
stock_vertical_lift/static/description/index.html
Normal file
524
stock_vertical_lift/static/description/index.html
Normal 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 <<a class="reference external" href="mailto:guewen.baconnier@camptocamp.com">guewen.baconnier@camptocamp.com</a>></li>
|
||||
</ul>
|
||||
<p>Trobz</p>
|
||||
<ul class="simple">
|
||||
<li>Dung Tran <<a class="reference external" href="mailto:dungtd@trobz.com">dungtd@trobz.com</a>></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>
|
||||
79
stock_vertical_lift/static/src/js/vertical_lift.js
Normal file
79
stock_vertical_lift/static/src/js/vertical_lift.js
Normal 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 {};
|
||||
});
|
||||
135
stock_vertical_lift/static/src/scss/vertical_lift.scss
Normal file
135
stock_vertical_lift/static/src/scss/vertical_lift.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
5
stock_vertical_lift/tests/__init__.py
Normal file
5
stock_vertical_lift/tests/__init__.py
Normal 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
|
||||
177
stock_vertical_lift/tests/common.py
Normal file
177
stock_vertical_lift/tests/common.py
Normal 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)
|
||||
166
stock_vertical_lift/tests/test_inventory.py
Normal file
166
stock_vertical_lift/tests/test_inventory.py
Normal 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)
|
||||
25
stock_vertical_lift/tests/test_lift_command.py
Normal file
25
stock_vertical_lift/tests/test_lift_command.py
Normal 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",
|
||||
}
|
||||
)
|
||||
52
stock_vertical_lift/tests/test_location.py
Normal file
52
stock_vertical_lift/tests/test_location.py
Normal 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")
|
||||
248
stock_vertical_lift/tests/test_pick.py
Normal file
248
stock_vertical_lift/tests/test_pick.py
Normal 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)
|
||||
160
stock_vertical_lift/tests/test_put.py
Normal file
160
stock_vertical_lift/tests/test_put.py
Normal 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)
|
||||
23
stock_vertical_lift/views/shuttle_screen_templates.xml
Normal file
23
stock_vertical_lift/views/shuttle_screen_templates.xml
Normal 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>
|
||||
58
stock_vertical_lift/views/stock_location_views.xml
Normal file
58
stock_vertical_lift/views/stock_location_views.xml
Normal 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>
|
||||
35
stock_vertical_lift/views/stock_move_line_views.xml
Normal file
35
stock_vertical_lift/views/stock_move_line_views.xml
Normal 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>
|
||||
20
stock_vertical_lift/views/stock_vertical_lift_templates.xml
Normal file
20
stock_vertical_lift/views/stock_vertical_lift_templates.xml
Normal 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>
|
||||
210
stock_vertical_lift/views/vertical_lift_operation_base_views.xml
Normal file
210
stock_vertical_lift/views/vertical_lift_operation_base_views.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
216
stock_vertical_lift/views/vertical_lift_shuttle_views.xml
Normal file
216
stock_vertical_lift/views/vertical_lift_shuttle_views.xml
Normal 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>
|
||||
1
stock_vertical_lift/wizards/__init__.py
Normal file
1
stock_vertical_lift/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import vertical_lift_shuttle_manual_barcode
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user