Skip to content

Commit

Permalink
Merge PR #2104 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by pedrobaeza
  • Loading branch information
OCA-git-bot committed Dec 16, 2024
2 parents 2defe76 + b41ea8e commit d4957f6
Show file tree
Hide file tree
Showing 11 changed files with 724 additions and 0 deletions.
6 changes: 6 additions & 0 deletions setup/stock_vlm_mgmt_kardex/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
86 changes: 86 additions & 0 deletions stock_vlm_mgmt_kardex/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
======================================
Kardex integration with stock_vlm_mgmt
======================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:cd51efe55ae6d7d00483d20033cb98572c7c06b4755e5b15ccdbe1077187f31f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |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/16.0/stock_vlm_mgmt_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-16-0/stock-logistics-warehouse-16-0-stock_vlm_mgmt_kardex
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-warehouse&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Integration with Kardex VLMs

**Table of contents**

.. contents::
:local:

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 to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_vlm_mgmt_kardex%0Aversion:%2016.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
~~~~~~~

* Tecnativa

Contributors
~~~~~~~~~~~~

* `Tecnativa <https://www.tecnativa.com>`_:

* David Vidal

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.

.. |maintainer-chienandalu| image:: https://github.com/chienandalu.png?size=40px
:target: https://github.com/chienandalu
:alt: chienandalu

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-chienandalu|

This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/16.0/stock_vlm_mgmt_kardex>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions stock_vlm_mgmt_kardex/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
14 changes: 14 additions & 0 deletions stock_vlm_mgmt_kardex/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2023 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Kardex integration with stock_vlm_mgmt",
"summary": "Light alternative for Kardex VLM integrations",
"version": "16.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"maintainers": ["chienandalu"],
"license": "AGPL-3",
"category": "Stock",
"depends": ["stock_vlm_mgmt"],
"data": [],
}
2 changes: 2 additions & 0 deletions stock_vlm_mgmt_kardex/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import stock_location
from . import kardex_request
159 changes: 159 additions & 0 deletions stock_vlm_mgmt_kardex/models/kardex_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Copyright 2022 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import socket

_logger = logging.getLogger(__name__)

KARDEX_KEYS = [
"task_type",
"task_id",
"address",
"carrier",
"pos_x",
"pos_y",
"qty",
"info1",
"info2",
"info3",
"info4",
]


KARDEX_OPERATION_CODES = {
"release": "0",
"put": "3",
"get": "4",
"count": "5",
}


class KardexRequest:
"""
Interface to Kardex SVMs
To command tasks we send a tcp message to the SVM with this format
TASK_TYPE;TASK_ID;ADDRESS;CARRIER;POS_X;POS_Y;QTY;INFO1;INFO2;INFO3;INFO4<CR><LF>
Each task is enqueued in the SVM and every time it gets finished it returns a
response with a success or error code (0 or 101).
- TASK_TYPE is configured at the machine itself.
- TASK_ID would be a unique id we give to the task so we can identify it when
the JMIF server responds back.
- ADDRESS: Which device to call.
- CARRIER: The tray we want to call
- POSX, POSY: Show the user where the items are located inside the tray
- QTY: How many items to get from the SVM
- The INFO fields are optional to show the user usefull info for the pick.
An example call:
3;001;21;6;7;1;40;PICK002;MAT02;Capacitor 40 mV;Check they're ok<CR><LF>
The message will get to the SVM screen. When the user finishes, the SVM will respond
back with a success message. Notice that in this response, the quantity has been
changed by the user:
0;001;21;6;7;1;25;PICK002;MAT02;Capacitor 40 mV;Check they're ok<CR><LF>
If there'd be an error in the process we'd get an error:
101;001;21;6;7;1;40;PICK002;MAT02;Capacitor 40 mV;Check they're ok<CR><LF>
If we want to release the carriers, we can call the carrier 0
0;465;21;0;0;0;0;;;;<CR><LF>
"""

def __init__(self, ip, port, timeout=0, **options):
self.ip = ip
self.port = int(port)
self.timeout = timeout
self.ignore_response = options.get("ignore_response")

def parse_data(self, data):
"""Transforms csv single string into a dictionary that we can work with.
@param {string} data: semicolon separated values for the kardex payload format
@return {dict} those csv values transformed into key value pairs
"""
parsed_data = dict.fromkeys(KARDEX_KEYS, None)
try:
parsed_data.update({k: v for k, v in zip(KARDEX_KEYS, data.split(";"))})
except Exception:
_logger.debug(f"Exception parsing data: {data}")
if parsed_data.get("qty"):
# Strip dots
parsed_data["qty"] = parsed_data["qty"].replace(".", "")
return parsed_data

def prepare_data(self, data):
"""Transforms data dict into kardex csv data string
@param {dict} data
@return {string}
"""
# No support for floats :S
data["qty"] = int(float(data["qty"]))
values = [str(v) for v in data.values()]
return f"{';'.join(values)}\r\n"

def request_operation(self, data):
"""
@param {string|dict} we can handle either a dictionary with the KARDEX_KEYS
format or the csv formatted values in a string (it must end in \r\n)
@return {dict} with the reponse of the device on the matching task
"""
if isinstance(data, dict):
# We receive the task type with a common code. Transform into the Kardex one
data["task_type"] = KARDEX_OPERATION_CODES.get(data["task_type"], "0")
data = self.prepare_data(data)
_logger.info(f"Request: {data}")
operation_id = self.parse_data(data).get("task_id")
if not operation_id:
return
data = data.encode("utf-8")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as device:
try:
device.connect((self.ip, self.port))
except ConnectionRefusedError:
return {"code": "-1", "task_id": operation_id}
device.sendall(data)
# Open the request with the optional timeout so the thread isn't halted
# forever
if self.timeout:
device.settimeout(self.timeout)
# Default response
response = {"code": "0", "task_id": operation_id}
# TODO: This is uncomplete: when we do the request, we won't receive the
# response until the VLM user validates the operation from the device screen
# That's an unknown amount of time.
# And what if the system is reset in the meantime? We'd loose the original
# thread and the response would be missed.
# I guess that's why the c2c module has that proxy thing, so they can be
# more resilient on that regard or at least to not block the thread waiting
# for a response.
while True and not self.ignore_response:
# TBE: Will this response window be enough to get it in one shot?
try:
res = device.recv(1024).decode("utf-8")
_logger.info(res)
except socket.timeout:
response["code"] = "-3"
return response
response = self.parse_data(res)
# Deal with response codes. Default to unkown issue code
response["code"] = response.get("task_type", "-999")
# Code 101 for an issue that happens in the VLM hardware itself
if response["code"] == "101":
response["code"] = "-4"
# Task cancelled
if response["code"] == "107":
response["code"] = "-5"
# If it's not our operation we should keep trying
# TBE: But until when? This locks the current thread so the screen
# is indeed locked and the user can't do virtually anything about it
if response["task_id"] == operation_id:
break
if response["task_id"] is None:
# Empty response. Unknow reason error. Could be due to a shutdown
# of the JMIF service.
return {"code": "-2", "task_id": operation_id}
_logger.info(f"Response: {response}")
return response
26 changes: 26 additions & 0 deletions stock_vlm_mgmt_kardex/models/stock_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2023 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models

from .kardex_request import KardexRequest


class StockLocation(models.Model):
_inherit = "stock.location"

vlm_vendor = fields.Selection(
selection_add=[
("kardex", "Kardex"),
],
)

def _kardex_vlm_connector(self) -> KardexRequest:
"""Wildcarded method to return our vendor specific connector"""
return KardexRequest

def send_vlm_request(self, data, **options):
"""The tray call in Kardex doesn't return anything, so we can release the
thread immediately"""
if self.vlm_vendor == "kardex" and self.env.context.get("vlm_tray_call"):
options["ignore_response"] = True
return super().send_vlm_request(data, **options)
3 changes: 3 additions & 0 deletions stock_vlm_mgmt_kardex/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* `Tecnativa <https://www.tecnativa.com>`_:

* David Vidal
1 change: 1 addition & 0 deletions stock_vlm_mgmt_kardex/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Integration with Kardex VLMs
Loading

0 comments on commit d4957f6

Please sign in to comment.