diff --git a/setup/stock_vertical_lift_kardex/odoo/addons/stock_vertical_lift_kardex b/setup/stock_vertical_lift_kardex/odoo/addons/stock_vertical_lift_kardex new file mode 120000 index 000000000..0c99c9c02 --- /dev/null +++ b/setup/stock_vertical_lift_kardex/odoo/addons/stock_vertical_lift_kardex @@ -0,0 +1 @@ +../../../../stock_vertical_lift_kardex \ No newline at end of file diff --git a/setup/stock_vertical_lift_kardex/setup.py b/setup/stock_vertical_lift_kardex/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_vertical_lift_kardex/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_vertical_lift_kardex/README.rst b/stock_vertical_lift_kardex/README.rst new file mode 100644 index 000000000..7e0a31618 --- /dev/null +++ b/stock_vertical_lift_kardex/README.rst @@ -0,0 +1,96 @@ +====================== +Vertical Lift - Kardex +====================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_vertical_lift_kardex + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_vertical_lift_kardex + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Add support for Kardex Remstar vertical lifts to the Vertical Lift +module. + +.. 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 `_ + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +* Add support of the hardware +* handle multiple gates for one lift + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Guewen Baconnier + +Trobz + +* Dung Tran + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* Camptocamp + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_vertical_lift_kardex/__init__.py b/stock_vertical_lift_kardex/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_vertical_lift_kardex/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_vertical_lift_kardex/__manifest__.py b/stock_vertical_lift_kardex/__manifest__.py new file mode 100644 index 000000000..6f3be2e20 --- /dev/null +++ b/stock_vertical_lift_kardex/__manifest__.py @@ -0,0 +1,15 @@ +# 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": "14.0.1.0.0", + "category": "Stock", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "depends": ["stock_vertical_lift", "stock_location_position"], + "website": "https://github.com/OCA/stock-logistics-warehouse", + "data": [], + "installable": True, + "development_status": "Alpha", +} diff --git a/stock_vertical_lift_kardex/i18n/stock_vertical_lift_kardex.pot b/stock_vertical_lift_kardex/i18n/stock_vertical_lift_kardex.pot new file mode 100644 index 000000000..1717a3051 --- /dev/null +++ b/stock_vertical_lift_kardex/i18n/stock_vertical_lift_kardex.pot @@ -0,0 +1,38 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_vertical_lift_kardex +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_vertical_lift_kardex +#: code:addons/stock_vertical_lift_kardex/models/stock_location.py:0 +#, python-format +msgid "" +"Cell location %s has no position. Check if the dimensions of tray %s are " +"properly set in the tray type." +msgstr "" + +#. module: stock_vertical_lift_kardex +#: model:ir.model,name:stock_vertical_lift_kardex.model_stock_location +msgid "Inventory Locations" +msgstr "" + +#. module: stock_vertical_lift_kardex +#: code:addons/stock_vertical_lift_kardex/models/stock_location.py:0 +#, python-format +msgid "Shuttle tray %s has no level. Please fix the configuration" +msgstr "" + +#. module: stock_vertical_lift_kardex +#: model:ir.model,name:stock_vertical_lift_kardex.model_vertical_lift_shuttle +msgid "Vertical Lift Shuttle" +msgstr "" diff --git a/stock_vertical_lift_kardex/models/__init__.py b/stock_vertical_lift_kardex/models/__init__.py new file mode 100644 index 000000000..51a3830f6 --- /dev/null +++ b/stock_vertical_lift_kardex/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_location +from . import vertical_lift_shuttle diff --git a/stock_vertical_lift_kardex/models/stock_location.py b/stock_vertical_lift_kardex/models/stock_location.py new file mode 100644 index 000000000..3b089845b --- /dev/null +++ b/stock_vertical_lift_kardex/models/stock_location.py @@ -0,0 +1,98 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, exceptions, models + +_logger = logging.getLogger(__name__) + + +class StockLocation(models.Model): + _inherit = "stock.location" + + def _hardware_kardex_prepare_fetch_payload(self, cell_location=None): + if self.level is False: + raise exceptions.UserError( + _("Shuttle tray %s has no level. " "Please fix the configuration") + % self.display_name + ) + shuttle = self.vertical_lift_shuttle_id + if cell_location: + x, y = cell_location.tray_cell_center_position() + if x == 0 and y == 0: + raise exceptions.UserError( + _( + "Cell location %s has no position. " + "Check if the dimensions of tray %s " + "are properly set in the tray type." + ) + % (cell_location.display_name, self.name) + ) + x, y = int(x), int(y) + else: + x, y = "", "" + subst = { + "code": shuttle._kardex_shuttle_code(), + "hostId": self.env["ir.sequence"].next_by_code("vertical.lift.command"), + # hard code the gate for now. + # TODO proper handling of multiple gates for 1 lift. + "addr": shuttle.name + "-1", + "carrier": self.level, + "carrierNext": "0", + "x": x, + "y": y, + "boxType": "", + "Q": "", + "order": "", + "part": "", + "desc": "", + } + return shuttle._hardware_kardex_format_template(subst) + + def _hardware_vertical_lift_fetch_tray_payload(self, cell_location=None): + """Prepare "fetch" message to be sent to the vertical lift hardware + + Private method, this is where the implementation actually happens. + Addons can add their instructions based on the hardware used for + this location. + + The hardware used for a location can be found in: + + ``self.vertical_lift_shuttle_id.hardware`` + + Each addon can implement its own mechanism depending of this value + and must call ``super``. + + The method must send the command to the vertical lift to fetch / open + the tray. If a ``cell_location`` is passed and if the hardware supports + a way to show a cell (such as a laser pointer), it should send this + command as well. + + Useful information that could be needed for the drivers: + + * Any field of `self` (name, barcode, ...) which is the current tray. + * Any field of `cell_location` (name, barcode, ...) which is the cell + in the tray. + * ``self.vertical_lift_shuttle_id`` is the current Shuttle, where we + find details about the hardware, the current mode (pick, put, ...). + * ``self.tray_type_id`` is the kind of tray. + * ``self.tray_type_id.width_per_cell`` and + ``self.tray_type_id.depth_per_cell`` return the size of a cell in mm. + * ``cell_location.posx`` and ``posy`` are the coordinate from the + bottom-left of the tray. + * ``cell_location.tray_cell_center_position()`` returns the central + position of the cell in mm from the bottom-left of a tray. (distance + from left, distance from bottom). Can be used for instance for + highlighting the cell using a laser pointer. + """ + if self.vertical_lift_shuttle_id.hardware == "kardex": + payload = self._hardware_kardex_prepare_fetch_payload( + cell_location=cell_location + ) + _logger.debug("Sending to kardex (fetch): {}", payload) + else: + payload = super()._hardware_vertical_lift_fetch_tray_payload( + cell_location=cell_location + ) + return payload diff --git a/stock_vertical_lift_kardex/models/vertical_lift_shuttle.py b/stock_vertical_lift_kardex/models/vertical_lift_shuttle.py new file mode 100644 index 000000000..305e1eeeb --- /dev/null +++ b/stock_vertical_lift_kardex/models/vertical_lift_shuttle.py @@ -0,0 +1,123 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +JMIF_STATUS = { + 0: "success", + 101: "common error", + 102: "sequence number invalid", + 103: "machine busy", + 104: "timeout", + 105: "max retry reached", + 106: "carrier in use or undefined", + 107: "cancelled", + 108: "invalid user input data", + 201: "request accepted and queued", + 202: "request processing started / request active", + 203: "carrier arrived, maybe overwritten by code 0", + 301: "AO occupied with other try on move back (store / put)", + 302: "AO occupied with other try on fetch (pick)", +} + + +class VerticalLiftShuttle(models.Model): + _inherit = "vertical.lift.shuttle" + + def _selection_hardware(self): + values = super()._selection_hardware() + values += [("kardex", "Kardex")] + return values + + _kardex_message_template = ( + "{code}|{hostId}|{addr}|{carrier}|{carrierNext}|" + "{x}|{y}|{boxType}|{Q}|{order}|{part}|{desc}|\r\n" + ) + + def _hardware_kardex_format_template(self, values): + payload = self._kardex_message_template.format(**values) + return payload.encode("iso-8859-1", "replace") + + def _kardex_shuttle_code(self): + mapping = {"pick": "1", "put": "2", "inventory": "5"} + ping = "61" + return mapping.get(self.mode, ping) + + def _hardware_kardex_prepare_release_payload(self): + subst = { + "code": self._kardex_shuttle_code(), + "hostId": self.env["ir.sequence"].next_by_code("vertical.lift.command"), + # hard code the gate for now. + "addr": self.name + "-1", + "carrier": "0", + "carrierNext": "0", + "x": "0", + "y": "0", + "boxType": "", + "Q": "", + "order": "", + "part": "", + "desc": "", + } + return self._hardware_kardex_format_template(subst) + + def _hardware_vertical_lift_release_tray_payload(self): + """Prepare "release" message to be sent to the vertical lift hardware + + Private method, this is where the implementation actually happens. + Addons can add their instructions based on the hardware used for + this location. + + The hardware used for a location can be found in: + + ``self.vertical_lift_shuttle_id.hardware`` + + Each addon can implement its own mechanism depending of this value + and must call ``super``. + + The method must send the command to the vertical lift to release (close) + the tray. + + Returns a message in bytes, that will be sent through + ``VerticalLiftShuttle._hardware_send_message()``. + """ + if self.hardware == "kardex": + payload = self._hardware_kardex_prepare_release_payload() + _logger.debug("Sending to kardex (release): {}", payload) + else: + payload = super()._hardware_vertical_lift_release_tray_payload() + return payload + + def _check_server_response(self, command): + response = command.answer + code, sep, remaining = response.partition("|") + code = int(code) + if code == 0: + return True + elif 1 <= code <= 99: + command.error = "interface error %d" % code + return False + elif code in JMIF_STATUS and code < 200: + command.error = "%d: %s" % (code, JMIF_STATUS[code]) + return False + elif code in JMIF_STATUS and code < 300: + command.error = "%d: %s" % (code, JMIF_STATUS[code]) + return True + elif code in JMIF_STATUS: + command.error = "%d: %s" % (code, JMIF_STATUS[code]) + elif 501 <= code <= 999: + command.error = "%d: %s" % (code, "MM260 Error") + elif 1000 <= code <= 32767: + command.error = "%d: %s" % (code, "C2000TCP/C3000CGI machine error") + elif 0xFF0 <= code == 0xFFF: + command.error = "{:x}: {}".format( + code, "C3000CGI machine error (global short)" + ) + elif 0xFFF < code: + command.error = "{:x}: {}".format(code, "C3000CGI machine error (long)") + return False diff --git a/stock_vertical_lift_kardex/proxy/kardex-proxy.py b/stock_vertical_lift_kardex/proxy/kardex-proxy.py new file mode 100755 index 000000000..97607999d --- /dev/null +++ b/stock_vertical_lift_kardex/proxy/kardex-proxy.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 +import argparse +import asyncio +import logging +import os +import ssl +import time + +import aiohttp + +_logger = logging.getLogger(__name__) + + +class KardexProxyProtocol(asyncio.Protocol): + def __init__(self, loop, queue, args): + _logger.info("Proxy created") + self.transport = None + self.buffer = b"" + self.queue = queue + self.loop = loop + self.args = args + + def connection_made(self, transport): + _logger.info("Proxy incoming cnx") + self.transport = transport + self.buffer = b"" + + def data_received(self, data): + self.buffer += data + _logger.info("Proxy: received %s", data) + if len(self.buffer) > 65535: + # prevent buffer overflow + self.transport.close() + + def eof_received(self): + _logger.info("Proxy: received EOF") + if self.buffer[-1] != b"\n": + # bad format -> close + self.transport.close() + data = ( + self.buffer.replace(b"\r\n", b"\n") + .replace(b"\n", b"\r\n") + .decode("iso-8859-1", "replace") + ) + self.loop.create_task(self.queue.put(data)) + self.buffer = b"" + + def connection_lost(self, exc): + self.transport = None + self.buffer = b"" + + +class KardexClientProtocol(asyncio.Protocol): + def __init__(self, loop, queue, args): + _logger.info("started kardex client") + self.loop = loop + self.queue = queue + self.args = args + self.transport = None + self.buffer = b"" + + def connection_made(self, transport): + self.transport = transport + _logger.info("connected to kardex server %r", transport) + + async def keepalive(self): + while True: + t = int(time.time()) + msg = "61|ping%d|SH1-1|0|0||||||||\r\n" % t + await self.send_message(msg) + await asyncio.sleep(20) + + async def send_message(self, message): + _logger.info("SEND %r", message) + message = message.encode("iso-8859-1") + self.transport.write(message) + + async def process_queue(self): + while True: + message = await self.queue.get() + await self.send_message(message) + + def data_received(self, data): + data = data.replace(b"\0", b"") + _logger.info("RECV %s", data) + self.buffer += data + if b"\r\n" in self.buffer: + msg, sep, rem = self.buffer.partition(b"\r\n") + self.buffer = rem + msg = msg.decode("iso-8859-1", "replace").strip() + if msg.startswith("0|ping"): + _logger.info("ping ok") + else: + _logger.info("notify odoo: %s", msg) + self.loop.create_task(self.notify_odoo(msg)) + + def connection_lost(self, exc): + self.loop.stop() + + async def notify_odoo(self, msg): + url = self.args.odoo_url + "/vertical-lift" + async with aiohttp.ClientSession() as session: + params = {"answer": msg, "secret": self.args.secret} + async with session.post(url, data=params) as resp: + resp_text = await resp.text() + _logger.info("Reponse from Odoo: %s %s", resp.status, resp_text) + + +def main(args, ssl_context=None): + logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" + ) + loop = asyncio.get_event_loop() + queue = asyncio.Queue(loop=loop) + # create the main server + coro = loop.create_server( + lambda: KardexProxyProtocol(loop, queue, args), host=args.host, port=args.port + ) + loop.run_until_complete(coro) + + # create the connection to the JMIF client + if args.kardex_use_tls: + if ssl_context is None: + ssl_context = ssl.create_default_context() + else: + ssl_context = None + coro = loop.create_connection( + lambda: KardexClientProtocol(loop, queue, args), + host=args.kardex_host, + port=args.kardex_port, + ssl=ssl_context, + ) + transport, client = loop.run_until_complete(coro) + loop.create_task(client.keepalive()) + loop.create_task(client.process_queue()) + loop.run_forever() + loop.close() + + +def make_parser(): + listen_address = os.environ.get("INTERFACE", "0.0.0.0") + listen_port = int(os.environ.get("PORT", "7654")) + secret = os.environ.get("ODOO_CALLBACK_SECRET", "") + odoo_url = os.environ.get("ODOO_URL", "http://localhost:8069") + odoo_db = os.environ.get("ODOO_DB", "odoodb") + kardex_host = os.environ.get("KARDEX_HOST", "kardex") + kardex_port = int(os.environ.get("KARDEX_PORT", "9600")) + kardex_use_tls = ( + False + if os.environ.get("KARDEX_TLS", "") in ("", "0", "False", "FALSE") + else True + ) + parser = argparse.ArgumentParser() + arguments = [ + ("--host", listen_address, str), + ("--port", listen_port, int), + ("--odoo-url", odoo_url, str), + ("--odoo-db", odoo_db, str), + ("--secret", secret, str), + ("--kardex-host", kardex_host, str), + ("--kardex-port", kardex_port, str), + ("--kardex-use-tls", kardex_use_tls, bool), + ] + for name, default, type_ in arguments: + parser.add_argument(name, default=default, action="store", type=type_) + return parser + + +if __name__ == "__main__": + parser = make_parser() + args = parser.parse_args() + main(args) diff --git a/stock_vertical_lift_kardex/proxy/requirements.txt b/stock_vertical_lift_kardex/proxy/requirements.txt new file mode 100644 index 000000000..ee4ba4f3d --- /dev/null +++ b/stock_vertical_lift_kardex/proxy/requirements.txt @@ -0,0 +1 @@ +aiohttp diff --git a/stock_vertical_lift_kardex/proxy/test.py b/stock_vertical_lift_kardex/proxy/test.py new file mode 100644 index 000000000..a7899f21a --- /dev/null +++ b/stock_vertical_lift_kardex/proxy/test.py @@ -0,0 +1,106 @@ +# pylint: disable=W8116 +import asyncio +import logging +import socket +import time + +_logger = logging.getLogger("kardex.proxy") +logging.basicConfig(level=logging.DEBUG) + + +class KardexProxyProtocol(asyncio.Protocol): + def __init__(self, loop, queue): + _logger.info("Proxy created") + self.transport = None + self.buffer = b"" + self.queue = queue + self.loop = loop + + def connection_made(self, transport): + _logger.info("Proxy incoming cnx") + self.transport = transport + self.buffer = b"" + + def data_received(self, data): + self.buffer += data + _logger.info("Proxy: received %s", data) + if len(self.buffer) > 65535: + # prevent buffer overflow + self.transport.close() + + def eof_received(self): + _logger.info("Proxy: received EOF") + if self.buffer[-1] != b"\n": + # bad format -> close + self.transport.close() + data = ( + self.buffer.replace(b"\r\n", b"\n") + .replace(b"\n", b"\r\n") + .decode("iso-8859-1", "replace") + ) + task = self.loop.create_task(self.queue.put(data)) + self.buffer = b"" + print("toto", task) + + def connection_lost(self, exc): + self.transport = None + self.buffer = b"" + + +class KardexClientProtocol(asyncio.Protocol): + def __init__(self, loop, queue): + _logger.info("started kardex client") + self.loop = loop + self.queue = queue + self.transport = None + self.buffer = b"" + + def connection_made(self, transport): + self.transport = transport + _logger.info("connected to kardex server %r", transport) + + async def keepalive(self): + while True: + t = int(time.time()) + msg = "61|ping%d|SH1-1|0|0||||||||\r\n" % t + await self.send_message(msg) + await asyncio.sleep(5) + + async def send_message(self, message): + _logger.info("SEND %s", message) + message = message.encode("iso-8859-1").ljust(1024, b"\0") + self.transport.write(message) + + async def process_queue(self): + while True: + message = await self.queue.get() + await self.send_message(message) + + def data_received(self, data): + data = data.replace(b"\0", b"") + _logger.info("RECV %s", data) + self.buffer += data + + def connection_lost(self, exc): + self.loop.stop() + + +if __name__ == "__main__": + _logger.info("starting") + loop = asyncio.get_event_loop() + loop.set_debug(1) + queue = asyncio.Queue(loop=loop) + coro = loop.create_server( + lambda: KardexProxyProtocol(loop, queue), port=3000, family=socket.AF_INET + ) + server = loop.run_until_complete(coro) + coro = loop.create_connection( + lambda: KardexClientProtocol(loop, queue), "localhost", 9600 + ) + transport, client = loop.run_until_complete(coro) + print("%r" % transport) + loop.create_task(client.keepalive()) + loop.create_task(client.process_queue()) + _logger.info("run loop") + loop.run_forever() + loop.close() diff --git a/stock_vertical_lift_kardex/readme/CONTRIBUTORS.rst b/stock_vertical_lift_kardex/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..36ac008db --- /dev/null +++ b/stock_vertical_lift_kardex/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +* Guewen Baconnier + +Trobz + +* Dung Tran diff --git a/stock_vertical_lift_kardex/readme/CREDITS.rst b/stock_vertical_lift_kardex/readme/CREDITS.rst new file mode 100644 index 000000000..f5cc070c7 --- /dev/null +++ b/stock_vertical_lift_kardex/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +* Camptocamp diff --git a/stock_vertical_lift_kardex/readme/DESCRIPTION.rst b/stock_vertical_lift_kardex/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c4b64e9b0 --- /dev/null +++ b/stock_vertical_lift_kardex/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +Add support for Kardex Remstar vertical lifts to the Vertical Lift +module. diff --git a/stock_vertical_lift_kardex/readme/ROADMAP.rst b/stock_vertical_lift_kardex/readme/ROADMAP.rst new file mode 100644 index 000000000..a96fc55be --- /dev/null +++ b/stock_vertical_lift_kardex/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Add support of the hardware +* handle multiple gates for one lift diff --git a/stock_vertical_lift_kardex/requirements.txt b/stock_vertical_lift_kardex/requirements.txt new file mode 100644 index 000000000..ee4ba4f3d --- /dev/null +++ b/stock_vertical_lift_kardex/requirements.txt @@ -0,0 +1 @@ +aiohttp diff --git a/stock_vertical_lift_kardex/static/description/icon.png b/stock_vertical_lift_kardex/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_vertical_lift_kardex/static/description/icon.png differ diff --git a/stock_vertical_lift_kardex/static/description/index.html b/stock_vertical_lift_kardex/static/description/index.html new file mode 100644 index 000000000..f997b87b3 --- /dev/null +++ b/stock_vertical_lift_kardex/static/description/index.html @@ -0,0 +1,446 @@ + + + + + + +Vertical Lift - Kardex + + + +
+

Vertical Lift - Kardex

+ + +

Alpha License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Add support for Kardex Remstar vertical lifts to the Vertical Lift +module.

+
+

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

+
+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Add support of the hardware
  • +
  • handle multiple gates for one lift
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +

Trobz

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Camptocamp
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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 project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ +