mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Add stock_vertical_lift module
This commit is contained in:
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,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
../../../../stock_vertical_lift_kardex
|
||||
6
setup/stock_vertical_lift_kardex/setup.py
Normal file
6
setup/stock_vertical_lift_kardex/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
134
stock_vertical_lift/README.rst
Normal file
134
stock_vertical_lift/README.rst
Normal file
@@ -0,0 +1,134 @@
|
||||
=============
|
||||
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/12.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-12-0/stock-logistics-warehouse-12-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/12.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://github.com/OCA/odoo-community.org/blob/master/website/Contribution/oca_module_lifecycle_development_status.rst>`_
|
||||
|
||||
**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.
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* Extract the tray types and matrix widget in a module, they can be used
|
||||
alone without vertical lift
|
||||
* Consider merging the 'vertical_lift_kind' with the kind added by
|
||||
stock_location_zone
|
||||
* Complete Pick screen and workflow (currently enough for a demo, not for production)
|
||||
* Implement Put-away screen and workflow
|
||||
* Implement Inventory screen 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:%2012.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>
|
||||
|
||||
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/12.0/stock_vertical_lift>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
1
stock_vertical_lift/__init__.py
Normal file
1
stock_vertical_lift/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
34
stock_vertical_lift/__manifest__.py
Normal file
34
stock_vertical_lift/__manifest__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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': '12.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
|
||||
],
|
||||
'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/vertical_lift_shuttle_views.xml',
|
||||
'views/stock_vertical_lift_templates.xml',
|
||||
'views/shuttle_screen_templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'development_status': 'Alpha',
|
||||
}
|
||||
32
stock_vertical_lift/demo/product_demo.xml
Normal file
32
stock_vertical_lift/demo/product_demo.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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="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"/>
|
||||
<field name="property_stock_inventory" ref="stock.location_inventory"/>
|
||||
</record>
|
||||
|
||||
<record id="product_recovery_socks" model="product.product">
|
||||
<field name="default_code">RS300</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"/>
|
||||
<field name="property_stock_inventory" ref="stock.location_inventory"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
20
stock_vertical_lift/demo/stock_inventory_demo.xml
Normal file
20
stock_vertical_lift/demo/stock_inventory_demo.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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">30.0</field>
|
||||
<field name="location_id" ref="stock_location_vertical_lift_demo_tray_1b_x3y2"/>
|
||||
</record>
|
||||
|
||||
<function model="stock.inventory" name="action_validate">
|
||||
<function eval="[[('state','=','draft'),('id', '=', ref('stock_vertical_lift.stock_inventory_vertical_lift_0'))]]" model="stock.inventory" name="search"/>
|
||||
</function>
|
||||
|
||||
</odoo>
|
||||
116
stock_vertical_lift/demo/stock_location_demo.xml
Normal file
116
stock_vertical_lift/demo/stock_location_demo.xml
Normal file
@@ -0,0 +1,116 @@
|
||||
<?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"/>
|
||||
<field name="company_id"></field>
|
||||
</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_16x"/>
|
||||
<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_32x"/>
|
||||
<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>
|
||||
30
stock_vertical_lift/demo/stock_picking_demo.xml
Normal file
30
stock_vertical_lift/demo/stock_picking_demo.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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>
|
||||
|
||||
</odoo>
|
||||
22
stock_vertical_lift/demo/vertical_lift_shuttle_demo.xml
Normal file
22
stock_vertical_lift/demo/vertical_lift_shuttle_demo.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
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 |
4
stock_vertical_lift/models/__init__.py
Normal file
4
stock_vertical_lift/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import vertical_lift_shuttle
|
||||
from . import stock_location
|
||||
from . import stock_move
|
||||
from . import stock_quant
|
||||
40
stock_vertical_lift/models/stock_location.py
Normal file
40
stock_vertical_lift/models/stock_location.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, 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,
|
||||
)
|
||||
|
||||
@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)
|
||||
if kind:
|
||||
location.vertical_lift_kind = kind
|
||||
23
stock_vertical_lift/models/stock_move.py
Normal file
23
stock_vertical_lift/models/stock_move.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
@api.multi
|
||||
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.shuttle. But we know that when the state
|
||||
# of any move line changes, we can invalidate them as the count of
|
||||
# assigned move lines may change (and we track this in stock.move,
|
||||
# not stock.move.line, becaus the state of the lines is a related
|
||||
# to this one).
|
||||
self.env['vertical.lift.shuttle'].invalidate_cache(
|
||||
['number_of_ops', 'number_of_ops_all']
|
||||
)
|
||||
return result
|
||||
16
stock_vertical_lift/models/stock_quant.py
Normal file
16
stock_vertical_lift/models/stock_quant.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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.shuttle. But we know that when the quantity
|
||||
# of quant changes, we can invalidate the field on the shuttles.
|
||||
self.env['vertical.lift.shuttle'].invalidate_cache(['tray_qty'])
|
||||
return result
|
||||
412
stock_vertical_lift/models/vertical_lift_shuttle.py
Normal file
412
stock_vertical_lift/models/vertical_lift_shuttle.py
Normal file
@@ -0,0 +1,412 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
from odoo.addons.base_sparse_field.models.fields import Serialized
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
current_move_line_id = fields.Many2one(comodel_name='stock.move.line')
|
||||
|
||||
number_of_ops = fields.Integer(
|
||||
compute='_compute_number_of_ops', string='Number of Operations'
|
||||
)
|
||||
number_of_ops_all = fields.Integer(
|
||||
compute='_compute_number_of_ops_all',
|
||||
string='Number of Operations in all shuttles',
|
||||
)
|
||||
|
||||
operation_descr = fields.Char(
|
||||
string="Operation",
|
||||
default="Scan New Destination Location",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# tray information (will come from stock.location or a new tray model)
|
||||
tray_location_id = fields.Many2one(
|
||||
comodel_name='stock.location',
|
||||
compute='_compute_tray_matrix',
|
||||
string='Tray Location',
|
||||
)
|
||||
tray_name = fields.Char(compute='_compute_tray_matrix', string='Tray Name')
|
||||
tray_type_id = fields.Many2one(
|
||||
comodel_name='stock.location.tray.type',
|
||||
compute='_compute_tray_matrix',
|
||||
string='Tray Type',
|
||||
)
|
||||
tray_type_code = fields.Char(
|
||||
compute='_compute_tray_matrix', string='Tray Code'
|
||||
)
|
||||
tray_x = fields.Integer(string='X', compute='_compute_tray_matrix')
|
||||
tray_y = fields.Integer(string='Y', compute='_compute_tray_matrix')
|
||||
tray_matrix = Serialized(string='Cells', compute='_compute_tray_matrix')
|
||||
tray_qty = fields.Float(
|
||||
string='Stock Quantity', compute='_compute_tray_qty'
|
||||
)
|
||||
|
||||
# current operation information
|
||||
picking_id = fields.Many2one(
|
||||
related='current_move_line_id.picking_id', readonly=True
|
||||
)
|
||||
picking_origin = fields.Char(
|
||||
related='current_move_line_id.picking_id.origin', readonly=True
|
||||
)
|
||||
picking_partner_id = fields.Many2one(
|
||||
related='current_move_line_id.picking_id.partner_id', readonly=True
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
related='current_move_line_id.product_id', readonly=True
|
||||
)
|
||||
product_uom_id = fields.Many2one(
|
||||
related='current_move_line_id.product_uom_id', readonly=True
|
||||
)
|
||||
product_uom_qty = fields.Float(
|
||||
related='current_move_line_id.product_uom_qty', readonly=True
|
||||
)
|
||||
product_packagings = fields.Html(
|
||||
string='Packaging', compute='_compute_product_packagings'
|
||||
)
|
||||
qty_done = fields.Float(
|
||||
related='current_move_line_id.qty_done', readonly=True
|
||||
)
|
||||
lot_id = fields.Many2one(
|
||||
related='current_move_line_id.lot_id', readonly=True
|
||||
)
|
||||
location_dest_id = fields.Many2one(
|
||||
string="Destination",
|
||||
related='current_move_line_id.location_dest_id',
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# TODO add a glue addon with product_expiry to add the field
|
||||
|
||||
_barcode_scanned = fields.Char(
|
||||
"Barcode Scanned",
|
||||
help="Value of the last barcode scanned.",
|
||||
store=False,
|
||||
)
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
# FIXME notify_info is only for the demo
|
||||
self.env.user.notify_info('Scanned barcode: {}'.format(barcode))
|
||||
method = 'on_barcode_scanned_{}'.format(self.mode)
|
||||
getattr(self, method)(barcode)
|
||||
|
||||
def on_barcode_scanned_pick(self, barcode):
|
||||
location = self.env['stock.location'].search(
|
||||
[('barcode', '=', barcode)]
|
||||
)
|
||||
if location:
|
||||
self.current_move_line_id.location_dest_id = location
|
||||
self.operation_descr = _('Save')
|
||||
else:
|
||||
self.env.user.notify_warning(
|
||||
_('No location found for barcode {}').format(barcode)
|
||||
)
|
||||
|
||||
def on_barcode_scanned_put(self, barcode):
|
||||
pass
|
||||
|
||||
def on_barcode_scanned_inventory(self, barcode):
|
||||
pass
|
||||
|
||||
@api.model
|
||||
def _selection_hardware(self):
|
||||
return [('simulation', 'Simulation')]
|
||||
|
||||
@api.depends('current_move_line_id.product_id.packaging_ids')
|
||||
def _compute_product_packagings(self):
|
||||
for record in self:
|
||||
if not record.current_move_line_id:
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
values = {
|
||||
'packagings': [
|
||||
{
|
||||
'name': pkg.name,
|
||||
'qty': pkg.qty,
|
||||
'unit': product.uom_id.name,
|
||||
}
|
||||
for pkg in product.packaging_ids
|
||||
]
|
||||
}
|
||||
content = self.env['ir.qweb'].render(
|
||||
'stock_vertical_lift.packagings', values
|
||||
)
|
||||
record.product_packagings = content
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops(self):
|
||||
for record in self:
|
||||
record.number_of_ops = record.count_move_lines_to_do()
|
||||
|
||||
@api.depends()
|
||||
def _compute_number_of_ops_all(self):
|
||||
for record in self:
|
||||
record.number_of_ops_all = record.count_move_lines_to_do_all()
|
||||
|
||||
@api.depends('tray_location_id', 'current_move_line_id.product_id')
|
||||
def _compute_tray_qty(self):
|
||||
for record in self:
|
||||
if not (record.tray_location_id and record.current_move_line_id):
|
||||
continue
|
||||
product = record.current_move_line_id.product_id
|
||||
quants = self.env['stock.quant'].search(
|
||||
[
|
||||
('location_id', '=', record.tray_location_id.id),
|
||||
('product_id', '=', product.id),
|
||||
]
|
||||
)
|
||||
record.tray_qty = sum(quants.mapped('quantity'))
|
||||
|
||||
@api.depends()
|
||||
def _compute_tray_matrix(self):
|
||||
for record in self:
|
||||
modes = {
|
||||
'pick': 'location_id',
|
||||
'put': 'location_dest_id',
|
||||
# TODO what to do for inventory?
|
||||
'inventory': 'location_id',
|
||||
}
|
||||
location = record.current_move_line_id[modes[record.mode]]
|
||||
tray_type = location.location_id.tray_type_id
|
||||
selected = []
|
||||
cells = []
|
||||
if location:
|
||||
selected = location._tray_cell_coords()
|
||||
cells = location._tray_cell_matrix()
|
||||
|
||||
# this is the current cell
|
||||
record.tray_location_id = location.id
|
||||
# name of the tray where the cell is
|
||||
record.tray_name = location.location_id.name
|
||||
record.tray_type_id = tray_type.id
|
||||
record.tray_type_code = tray_type.code
|
||||
record.tray_x = location.posx
|
||||
record.tray_y = location.posy
|
||||
record.tray_matrix = {
|
||||
# x, y: position of the selected cell
|
||||
'selected': selected,
|
||||
# 0 is empty, 1 is not
|
||||
'cells': cells,
|
||||
}
|
||||
|
||||
def _domain_move_lines_to_do(self):
|
||||
domain = [
|
||||
# TODO check state
|
||||
('state', '=', 'assigned')
|
||||
]
|
||||
domain_extensions = {
|
||||
'pick': [('location_id', 'child_of', self.location_id.id)],
|
||||
# TODO ensure that we cannot have the same ml in 2 shuttles (cannot
|
||||
# happen with 'pick' as they are in the shuttle's location)
|
||||
'put': [('location_dest_id', 'child_of', self.location_id.id)],
|
||||
# TODO
|
||||
'inventory': [('id', '=', 0)],
|
||||
}
|
||||
return domain + domain_extensions[self.mode]
|
||||
|
||||
def _domain_move_lines_to_do_all(self):
|
||||
domain = [
|
||||
# TODO check state
|
||||
('state', '=', 'assigned')
|
||||
]
|
||||
# TODO search only in the view being a parent of shuttle's location
|
||||
shuttle_locations = self.env['stock.location'].search(
|
||||
[('vertical_lift_kind', '=', 'view')]
|
||||
)
|
||||
domain_extensions = {
|
||||
'pick': [('location_id', 'child_of', shuttle_locations.ids)],
|
||||
'put': [('location_dest_id', 'child_of', shuttle_locations.ids)],
|
||||
# TODO
|
||||
'inventory': [('id', '=', 0)],
|
||||
}
|
||||
return domain + domain_extensions[self.mode]
|
||||
|
||||
def count_move_lines_to_do(self):
|
||||
self.ensure_one()
|
||||
return self.env['stock.move.line'].search_count(
|
||||
self._domain_move_lines_to_do()
|
||||
)
|
||||
|
||||
def count_move_lines_to_do_all(self):
|
||||
self.ensure_one()
|
||||
return self.env['stock.move.line'].search_count(
|
||||
self._domain_move_lines_to_do_all()
|
||||
)
|
||||
|
||||
def button_release(self):
|
||||
if self.current_move_line_id:
|
||||
self._hardware_switch_off_laser_pointer()
|
||||
self._hardware_close_tray()
|
||||
self.select_next_move_line()
|
||||
if not self.current_move_line_id:
|
||||
# sorry not sorry
|
||||
return {
|
||||
'effect': {
|
||||
'fadeout': 'slow',
|
||||
'message': _('Congrats, you cleared the queue!'),
|
||||
'img_url': '/web/static/src/img/smile.svg',
|
||||
'type': 'rainbow_man',
|
||||
}
|
||||
}
|
||||
|
||||
def process_current_pick(self):
|
||||
# test code, TODO the smart one
|
||||
# (scan of barcode increments qty, save calls action_done?)
|
||||
line = self.current_move_line_id
|
||||
if line.state != 'done':
|
||||
line.qty_done = line.product_qty
|
||||
line.move_id._action_done()
|
||||
|
||||
def process_current_put(self):
|
||||
raise exceptions.UserError(_('Put workflow not implemented'))
|
||||
|
||||
def process_current_inventory(self):
|
||||
raise exceptions.UserError(_('Inventory workflow not implemented'))
|
||||
|
||||
def button_save(self):
|
||||
if not (self and self.current_move_line_id):
|
||||
return
|
||||
self.ensure_one()
|
||||
method = 'process_current_{}'.format(self.mode)
|
||||
getattr(self, method)()
|
||||
self.operation_descr = _('Release')
|
||||
|
||||
def select_next_move_line(self):
|
||||
self.ensure_one()
|
||||
next_move_line = self.env['stock.move.line'].search(
|
||||
self._domain_move_lines_to_do(), limit=1
|
||||
)
|
||||
self.current_move_line_id = next_move_line
|
||||
# TODO use a state machine to define next steps and
|
||||
# description?
|
||||
descr = (
|
||||
_('Scan New Destination Location')
|
||||
if next_move_line
|
||||
else _('No operations')
|
||||
)
|
||||
self.operation_descr = descr
|
||||
if next_move_line:
|
||||
self._hardware_switch_on_laser_pointer()
|
||||
self._hardware_open_tray()
|
||||
|
||||
def action_open_screen(self):
|
||||
self.select_next_move_line()
|
||||
self.ensure_one()
|
||||
screen_xmlid = (
|
||||
'stock_vertical_lift.vertical_lift_shuttle_view_form_screen'
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
'views': [[self.env.ref(screen_xmlid).id, 'form']],
|
||||
'res_id': self.id,
|
||||
'target': 'fullscreen',
|
||||
'flags': {
|
||||
'headless': True,
|
||||
'form_view_initial_mode': 'edit',
|
||||
'no_breadcrumbs': True,
|
||||
},
|
||||
}
|
||||
|
||||
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_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.select_next_move_line()
|
||||
|
||||
def switch_put(self):
|
||||
self.mode = 'put'
|
||||
self.select_next_move_line()
|
||||
|
||||
def switch_inventory(self):
|
||||
self.mode = 'inventory'
|
||||
self.select_next_move_line()
|
||||
|
||||
def _hardware_switch_on_laser_pointer(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Laser pointer on x{} y{}').format(
|
||||
self.tray_x, self.tray_y
|
||||
),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_switch_off_laser_pointer(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Switch off laser pointer'),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_open_tray(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Opening tray {}').format(self.tray_name),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
def _hardware_close_tray(self):
|
||||
if self.hardware == 'simulation':
|
||||
self.env.user.notify_info(
|
||||
message=_('Closing tray {}').format(self.tray_name),
|
||||
title=_('Lift Simulation'),
|
||||
)
|
||||
|
||||
|
||||
class VerticalLiftShuttleManualBarcode(models.TransientModel):
|
||||
_name = 'vertical.lift.shuttle.manual.barcode'
|
||||
_description = 'Action to input a barcode'
|
||||
|
||||
barcode = fields.Char(string="Barcode")
|
||||
|
||||
@api.multi
|
||||
def button_save(self):
|
||||
shuttle_id = self.env.context.get('active_id')
|
||||
shuttle = self.env['vertical.lift.shuttle'].browse(shuttle_id).exists()
|
||||
if not shuttle:
|
||||
return
|
||||
if self.barcode:
|
||||
shuttle.on_barcode_scanned(self.barcode)
|
||||
39
stock_vertical_lift/readme/CONFIGURE.rst
Normal file
39
stock_vertical_lift/readme/CONFIGURE.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
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.
|
||||
1
stock_vertical_lift/readme/CONTRIBUTORS.rst
Normal file
1
stock_vertical_lift/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
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.
|
||||
3
stock_vertical_lift/readme/ROADMAP.rst
Normal file
3
stock_vertical_lift/readme/ROADMAP.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
* Complete Pick screen and workflow (currently enough for a demo, not for production)
|
||||
* Implement Put-away screen and workflow
|
||||
* Implement Inventory screen and workflow
|
||||
3
stock_vertical_lift/security/ir.model.access.csv
Normal file
3
stock_vertical_lift/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
||||
|
490
stock_vertical_lift/static/description/index.html
Normal file
490
stock_vertical_lift/static/description/index.html
Normal file
@@ -0,0 +1,490 @@
|
||||
<?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.14: 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/12.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-12-0/stock-logistics-warehouse-12-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/12.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://github.com/OCA/odoo-community.org/blob/master/website/Contribution/oca_module_lifecycle_development_status.rst">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>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#known-issues-roadmap" id="id6">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id7">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id8">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id9">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id10">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id11">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>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id6">Known issues / Roadmap</a></h1>
|
||||
<ul class="simple">
|
||||
<li>Extract the tray types and matrix widget in a module, they can be used
|
||||
alone without vertical lift</li>
|
||||
<li>Consider merging the ‘vertical_lift_kind’ with the kind added by
|
||||
stock_location_zone</li>
|
||||
<li>Complete Pick screen and workflow (currently enough for a demo, not for production)</li>
|
||||
<li>Implement Put-away screen and workflow</li>
|
||||
<li>Implement Inventory screen and workflow</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id7">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:%2012.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="#id8">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id9">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Camptocamp</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id10">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>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id11">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/12.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>
|
||||
55
stock_vertical_lift/static/src/js/vertical_lift.js
Normal file
55
stock_vertical_lift/static/src/js/vertical_lift.js
Normal file
@@ -0,0 +1,55 @@
|
||||
odoo.define('stock_vertical_lift.vertical_lift', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var KanbanRecord = require('web.KanbanRecord');
|
||||
var basicFields = require('web.basic_fields');
|
||||
var field_registry = require('web.field_registry');
|
||||
var FieldInteger = basicFields.FieldInteger;
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var ExitButton = FieldInteger.extend({
|
||||
tagName: 'button',
|
||||
className: 'btn btn-danger btn-block btn-lg o_shuttle_exit',
|
||||
events: {
|
||||
'click': '_onClick',
|
||||
},
|
||||
_render: function () {
|
||||
this.$el.text(this.string);
|
||||
},
|
||||
_onClick: function () {
|
||||
// the only reason to have this field widget is to be able
|
||||
// to inject clear_breadcrumbs in the action:
|
||||
// it will revert back to a normal - non-headless - view
|
||||
this.do_action('stock_vertical_lift.vertical_lift_shuttle_action', {
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
field_registry.add('vlift_shuttle_exit_button', ExitButton);
|
||||
|
||||
return {
|
||||
ExitButton: ExitButton,
|
||||
};
|
||||
|
||||
});
|
||||
124
stock_vertical_lift/static/src/scss/vertical_lift.scss
Normal file
124
stock_vertical_lift/static/src/scss/vertical_lift.scss
Normal file
@@ -0,0 +1,124 @@
|
||||
.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: 2.0em;
|
||||
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_shuttle_exit {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.o_vlift_shuttle_manual_barcode {
|
||||
.o_field_char {
|
||||
padding: 1em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 1em;
|
||||
font-size: 2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
stock_vertical_lift/tests/__init__.py
Normal file
2
stock_vertical_lift/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import test_location
|
||||
from . import test_vertical_lift_shuttle
|
||||
49
stock_vertical_lift/tests/common.py
Normal file
49
stock_vertical_lift/tests/common.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
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.vertical_lift_loc = cls.env.ref(
|
||||
'stock_vertical_lift.stock_location_vertical_lift'
|
||||
)
|
||||
|
||||
@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,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
40
stock_vertical_lift/tests/test_location.py
Normal file
40
stock_vertical_lift/tests/test_location.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
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_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')
|
||||
252
stock_vertical_lift/tests/test_vertical_lift_shuttle.py
Normal file
252
stock_vertical_lift/tests/test_vertical_lift_shuttle.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import unittest
|
||||
|
||||
from odoo import _, exceptions
|
||||
|
||||
from .common import VerticalLiftCase
|
||||
|
||||
|
||||
class TestVerticalLiftTrayType(VerticalLiftCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.picking_out = cls.env.ref(
|
||||
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
|
||||
)
|
||||
# we have a move line to pick created by demo picking
|
||||
# stock_picking_out_demo_vertical_lift_1
|
||||
cls.out_move_line = cls.picking_out.move_line_ids
|
||||
|
||||
def test_switch_pick(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.assertEqual(self.shuttle.mode, 'pick')
|
||||
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
|
||||
|
||||
def test_switch_put(self):
|
||||
self.shuttle.switch_put()
|
||||
self.assertEqual(self.shuttle.mode, 'put')
|
||||
# TODO check that we have an incoming move when switching
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id,
|
||||
self.env['stock.move.line'].browse(),
|
||||
)
|
||||
|
||||
def test_switch_inventory(self):
|
||||
self.shuttle.switch_inventory()
|
||||
self.assertEqual(self.shuttle.mode, 'inventory')
|
||||
# TODO check that we have what we should (what?)
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id,
|
||||
self.env['stock.move.line'].browse(),
|
||||
)
|
||||
|
||||
def test_pick_action_open_screen(self):
|
||||
self.shuttle.switch_pick()
|
||||
action = self.shuttle.action_open_screen()
|
||||
self.assertTrue(self.shuttle.current_move_line_id)
|
||||
self.assertEqual(action['type'], 'ir.actions.act_window')
|
||||
self.assertEqual(action['res_model'], 'vertical.lift.shuttle')
|
||||
self.assertEqual(action['res_id'], self.shuttle.id)
|
||||
|
||||
def test_pick_select_next_move_line(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.select_next_move_line()
|
||||
self.assertEqual(self.shuttle.current_move_line_id, self.out_move_line)
|
||||
self.assertEqual(
|
||||
self.shuttle.operation_descr,
|
||||
_('Scan New Destination Location')
|
||||
)
|
||||
|
||||
def test_pick_save(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
self.shuttle.button_save()
|
||||
self.assertEqual(
|
||||
self.shuttle.current_move_line_id.state,
|
||||
'done'
|
||||
)
|
||||
self.assertEqual(self.shuttle.operation_descr, _('Release'))
|
||||
|
||||
def test_pick_related_fields(self):
|
||||
self.shuttle.switch_pick()
|
||||
ml = self.shuttle.current_move_line_id = self.out_move_line
|
||||
|
||||
# Trays related fields
|
||||
# For pick, this is the source location, which is the cell where the
|
||||
# product is.
|
||||
self.assertEqual(self.shuttle.tray_location_id, ml.location_id)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_name,
|
||||
# parent = tray
|
||||
ml.location_id.location_id.name,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_type_id,
|
||||
# the tray type is on the parent of the cell (on the tray)
|
||||
ml.location_id.location_id.tray_type_id,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_type_code,
|
||||
ml.location_id.location_id.tray_type_id.code,
|
||||
)
|
||||
self.assertEqual(self.shuttle.tray_x, ml.location_id.posx)
|
||||
self.assertEqual(self.shuttle.tray_y, ml.location_id.posy)
|
||||
|
||||
# Move line related fields
|
||||
self.assertEqual(self.shuttle.picking_id, ml.picking_id)
|
||||
self.assertEqual(self.shuttle.picking_origin, ml.picking_id.origin)
|
||||
self.assertEqual(
|
||||
self.shuttle.picking_partner_id, ml.picking_id.partner_id
|
||||
)
|
||||
self.assertEqual(self.shuttle.product_id, ml.product_id)
|
||||
self.assertEqual(self.shuttle.product_uom_id, ml.product_uom_id)
|
||||
self.assertEqual(self.shuttle.product_uom_qty, ml.product_uom_qty)
|
||||
self.assertEqual(self.shuttle.qty_done, ml.qty_done)
|
||||
self.assertEqual(self.shuttle.lot_id, ml.lot_id)
|
||||
|
||||
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
|
||||
self.env.ref(
|
||||
'stock_vertical_lift.stock_picking_out_demo_vertical_lift_1'
|
||||
).action_cancel()
|
||||
|
||||
# ensure that we have stock in some cells, we'll put product1
|
||||
# in the first Shuttle and product2 in the second
|
||||
cell1 = self.env.ref(
|
||||
'stock_vertical_lift.'
|
||||
'stock_location_vertical_lift_demo_tray_1a_x3y2'
|
||||
)
|
||||
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
|
||||
shuttle2 = self.env.ref(
|
||||
'stock_vertical_lift.stock_vertical_lift_demo_shuttle_2'
|
||||
)
|
||||
|
||||
self.assertEqual(shuttle1.number_of_ops, 4)
|
||||
self.assertEqual(shuttle2.number_of_ops, 2)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 6)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 6)
|
||||
|
||||
# Process a line, should change the numbers.
|
||||
shuttle1.select_next_move_line()
|
||||
shuttle1.process_current_pick()
|
||||
self.assertEqual(shuttle1.number_of_ops, 3)
|
||||
self.assertEqual(shuttle2.number_of_ops, 2)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 5)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 5)
|
||||
|
||||
# add stock and make the last one assigned to check the number is
|
||||
# updated
|
||||
self._update_quantity_in_cell(cell2, product2, 10)
|
||||
unassigned.action_assign()
|
||||
self.assertEqual(shuttle1.number_of_ops, 3)
|
||||
self.assertEqual(shuttle2.number_of_ops, 3)
|
||||
self.assertEqual(shuttle1.number_of_ops_all, 6)
|
||||
self.assertEqual(shuttle2.number_of_ops_all, 6)
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
def test_put_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
def test_inventory_count_move_lines(self):
|
||||
pass
|
||||
|
||||
@unittest.skip('Not implemented')
|
||||
def test_on_barcode_scanned(self):
|
||||
# test to implement when the code is implemented
|
||||
pass
|
||||
|
||||
def test_button_release(self):
|
||||
# for the test, we'll consider our last line has been delivered
|
||||
self.out_move_line.qty_done = self.out_move_line.product_qty
|
||||
self.out_move_line.move_id._action_done()
|
||||
# release, no further operation in queue
|
||||
result = self.shuttle.button_release()
|
||||
self.assertFalse(self.shuttle.current_move_line_id)
|
||||
self.assertEqual(self.shuttle.operation_descr, _('No operations'))
|
||||
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_process_current_pick(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
qty_to_process = self.out_move_line.product_qty
|
||||
self.shuttle.process_current_pick()
|
||||
self.assertEqual(self.out_move_line.state, 'done')
|
||||
self.assertEqual(self.out_move_line.qty_done, qty_to_process)
|
||||
|
||||
def test_process_current_put(self):
|
||||
# test to implement when the code is implemented
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
self.shuttle.process_current_put()
|
||||
|
||||
def test_process_current_inventory(self):
|
||||
# test to implement when the code is implemented
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
self.shuttle.process_current_inventory()
|
||||
|
||||
def test_matrix(self):
|
||||
self.shuttle.switch_pick()
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
location = self.out_move_line.location_id
|
||||
# offset by -1 because the fields are for humans
|
||||
expected_x = location.posx - 1
|
||||
expected_y = location.posy - 1
|
||||
self.assertEqual(
|
||||
self.shuttle.tray_matrix,
|
||||
{
|
||||
'selected': [expected_x, expected_y],
|
||||
# fmt: off
|
||||
'cells': [
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 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
|
||||
self.shuttle.current_move_line_id = self.out_move_line
|
||||
self._update_quantity_in_cell(cell, self.out_move_line.product_id, 50)
|
||||
self.assertEqual(self.shuttle.tray_qty, 50)
|
||||
self._update_quantity_in_cell(cell, self.out_move_line.product_id, -20)
|
||||
self.assertEqual(self.shuttle.tray_qty, 30)
|
||||
17
stock_vertical_lift/views/shuttle_screen_templates.xml
Normal file
17
stock_vertical_lift/views/shuttle_screen_templates.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<odoo>
|
||||
|
||||
<template id="packagings">
|
||||
<ul class="o_vlift_packaging list-unstyled">
|
||||
<t t-foreach="packagings" 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="packaging['unit']"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
35
stock_vertical_lift/views/stock_location_views.xml
Normal file
35
stock_vertical_lift/views/stock_location_views.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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">
|
||||
<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>
|
||||
<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>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
11
stock_vertical_lift/views/stock_vertical_lift_templates.xml
Normal file
11
stock_vertical_lift/views/stock_vertical_lift_templates.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?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"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
287
stock_vertical_lift/views/vertical_lift_shuttle_views.xml
Normal file
287
stock_vertical_lift/views/vertical_lift_shuttle_views.xml
Normal file
@@ -0,0 +1,287 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="vertical_lift_shuttle_view_form_screen" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.shuttle.view.form.screen</field>
|
||||
<field name="model">vertical.lift.shuttle</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Operations" delete="0" create="0" class="o_vlift_shuttle">
|
||||
<!-- <field name="product_id" invisible="1"/> -->
|
||||
<div class="o_shuttle_header">
|
||||
<div class="o_shuttle_header_left o_shuttle_header_content">
|
||||
<field name="name" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_center o_shuttle_header_content">
|
||||
<label for="mode"/>
|
||||
<field name="mode" readonly="1"/>
|
||||
</div>
|
||||
<div class="o_shuttle_header_right o_shuttle_header_content">
|
||||
<label for="number_of_ops"/>
|
||||
<field name="number_of_ops" readonly="1"/> /
|
||||
<field name="number_of_ops_all" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_actions">
|
||||
<div class="o_shuttle_content">
|
||||
<button name="action_menu" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-bars"
|
||||
aria-label="Dropdown menu" title="Dropdown menu"/>
|
||||
<button name="action_manual_barcode" type="object"
|
||||
class="btn-secondary o_shuttle_icon_btn"
|
||||
string="" icon="fa-terminal"
|
||||
aria-label="Barcode Input" title="Barcode Input"/>
|
||||
</div>
|
||||
<div class="o_shuttle_content o_shuttle_content_right">
|
||||
<div>
|
||||
<!-- will react on barcode 'O-BTN.save' -->
|
||||
<button name="button_save"
|
||||
type="object"
|
||||
string="Save"
|
||||
icon="fa-check"
|
||||
class="btn-primary"
|
||||
barcode_trigger="save"/>
|
||||
<!-- will react on barcode 'O-BTN.release -->
|
||||
<button name="button_release"
|
||||
type="object"
|
||||
string="Release"
|
||||
class="btn-primary"
|
||||
barcode_trigger="release"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_operation bg-primary jumbotron jumbotron-fluid">
|
||||
<div class="container">
|
||||
<field name="operation_descr"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_shuttle_data" attrs="{'invisible': [('current_move_line_id', '=', False)]}">
|
||||
<!-- on the left of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_move">
|
||||
<div>
|
||||
<group>
|
||||
<field name="current_move_line_id" invisible="1"/>
|
||||
<label for="picking_id"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="picking_id" options="{'no_open': True}" class="mr8"/>
|
||||
<span>/</span>
|
||||
<field name="picking_origin" class="oe_inline ml8"/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="picking_partner_id"/>
|
||||
<div>
|
||||
<field name="picking_partner_id" options="{'no_open': True}"/>
|
||||
</div>
|
||||
<label for="location_dest_id"/>
|
||||
<div>
|
||||
<field name="location_dest_id"
|
||||
class="bg-primary o_shuttle_highlight"
|
||||
options="{'no_open': True}"/>
|
||||
</div>
|
||||
<label for="product_id"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="product_id" options="{'no_open': True}"/>
|
||||
</h1>
|
||||
</div>
|
||||
<div colspan="2">
|
||||
<field name="product_packagings"/>
|
||||
</div>
|
||||
<field name="lot_id"/>
|
||||
<label for="product_uom_qty" string="Quantity" class="ml32"/>
|
||||
<div colspan="2" class="ml32">
|
||||
<h1 class="bg-primary o_shuttle_highlight">
|
||||
<field name="product_uom_qty" class="oe_inline text-white"/>
|
||||
<field name="product_uom_id" options="{'no_open': True}" class="oe_inline ml8 text-white"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<!-- on the right of the screen -->
|
||||
<div class="o_shuttle_data_content o_shuttle_tray">
|
||||
<group col="1">
|
||||
<field name="tray_name"/>
|
||||
<field name="tray_type_code"/>
|
||||
<field name="tray_y"/>
|
||||
<field name="tray_x"/>
|
||||
<label for="tray_qty"/>
|
||||
<div colspan="2" class="oe_title">
|
||||
<h1>
|
||||
<field name="tray_qty"/>
|
||||
</h1>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<div>
|
||||
<field name="tray_matrix"
|
||||
widget="location_tray_matrix"
|
||||
/>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="vertical_lift_shuttle_form_menu" model="ir.ui.view">
|
||||
<field name="name">vertical.lift.shuttle.view.form.menu</field>
|
||||
<field name="model">vertical.lift.shuttle</field>
|
||||
<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">
|
||||
<field name="id" string="Back to settings"
|
||||
widget="vlift_shuttle_exit_button" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<footer></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_manual_barcode">
|
||||
<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></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="Operations">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="mode"/>
|
||||
<field name="location_id"/>
|
||||
<field name="hardware"/>
|
||||
<!-- move line visible and editable for prototyping purposes, to
|
||||
remove later -->
|
||||
<field name="current_move_line_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</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_type">form</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_kardex/__init__.py
Normal file
1
stock_vertical_lift_kardex/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
17
stock_vertical_lift_kardex/__manifest__.py
Normal file
17
stock_vertical_lift_kardex/__manifest__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
'name': 'Vertical Lift - Kardex',
|
||||
'summary': 'Integrate with Kardex Remstar Vertical Lifts',
|
||||
'version': '12.0.1.0.0',
|
||||
'category': 'Stock',
|
||||
'author': 'Camptocamp, Odoo Community Association (OCA)',
|
||||
'license': 'AGPL-3',
|
||||
'depends': [
|
||||
'stock_vertical_lift',
|
||||
],
|
||||
'website': 'https://www.camptocamp.com',
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'development_status': 'Alpha',
|
||||
}
|
||||
1
stock_vertical_lift_kardex/models/__init__.py
Normal file
1
stock_vertical_lift_kardex/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import vertical_lift_shuttle
|
||||
14
stock_vertical_lift_kardex/models/vertical_lift_shuttle.py
Normal file
14
stock_vertical_lift_kardex/models/vertical_lift_shuttle.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class VerticalLiftShuttle(models.Model):
|
||||
_inherit = 'vertical.lift.shuttle'
|
||||
|
||||
@api.model
|
||||
def _selection_hardware(self):
|
||||
values = super()._selection_hardware()
|
||||
values += [('kardex', 'Kardex')]
|
||||
return values
|
||||
1
stock_vertical_lift_kardex/readme/CONTRIBUTORS.rst
Normal file
1
stock_vertical_lift_kardex/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
2
stock_vertical_lift_kardex/readme/DESCRIPTION.rst
Normal file
2
stock_vertical_lift_kardex/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
Add support for Kardex Remstar vertical lifts to the Vertical Lift
|
||||
module.
|
||||
1
stock_vertical_lift_kardex/readme/ROADMAP.rst
Normal file
1
stock_vertical_lift_kardex/readme/ROADMAP.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Add support of the hardware
|
||||
Reference in New Issue
Block a user