mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Migrate stock_cubiscan to 13.0
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
{
|
||||
"name": "Stock Cubiscan",
|
||||
"summary": "Implement inteface with Cubiscan devices for packaging",
|
||||
"version": "12.0.1.0.0",
|
||||
"category": "Stock",
|
||||
"author": "Camptocamp",
|
||||
"version": "13.0.1.0.0",
|
||||
"category": "Warehouse",
|
||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"barcodes",
|
||||
@@ -14,7 +14,8 @@
|
||||
"product_packaging_dimension",
|
||||
"product_packaging_type_required",
|
||||
],
|
||||
"website": "http://www.camptocamp.com",
|
||||
"external_dependencies": {"python": ["cubiscan"]},
|
||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||
"data": [
|
||||
"views/assets.xml",
|
||||
"views/cubiscan_view.xml",
|
||||
|
||||
@@ -25,23 +25,15 @@ class CubiscanDevice(models.Model):
|
||||
[("not_ready", "Not Ready"), ("ready", "Ready")],
|
||||
default="not_ready",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@api.constrains("device_address", "port")
|
||||
def _check_connection_infos(self):
|
||||
self.ensure_one()
|
||||
if not 1 <= self.port <= 65535:
|
||||
raise ValidationError("Port must be in range 1-65535")
|
||||
raise ValidationError(_("Port must be in range 1-65535"))
|
||||
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
if not default:
|
||||
default = dict()
|
||||
default["state"] = "not_ready"
|
||||
return super().copy(default)
|
||||
|
||||
@api.multi
|
||||
def open_wizard(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
@@ -50,18 +42,20 @@ class CubiscanDevice(models.Model):
|
||||
"type": "ir.actions.act_window",
|
||||
"view_id": False,
|
||||
"view_mode": "form",
|
||||
"view_type": "form",
|
||||
"context": {"default_device_id": self.id},
|
||||
"target": "fullscreen",
|
||||
"flags": {
|
||||
"headless": True,
|
||||
"withControlPanel": False,
|
||||
"form_view_initial_mode": "edit",
|
||||
"no_breadcrumbs": True,
|
||||
},
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _get_interface(self):
|
||||
"""Return the CubiScan client
|
||||
|
||||
Can be overrided to customize the way it is instanciated
|
||||
"""
|
||||
self.ensure_one()
|
||||
ctx = SSL.create_default_context()
|
||||
ctx.load_cert_chain("/usr/lib/ssl/certs/camptocamp.pem")
|
||||
@@ -69,8 +63,8 @@ class CubiscanDevice(models.Model):
|
||||
ctx.verify_mode = SSL.CERT_NONE
|
||||
return CubiScan(self.device_address, self.port, self.timeout, ssl=ctx)
|
||||
|
||||
@api.multi
|
||||
def test_device(self):
|
||||
"""Check connection with the Cubiscan device"""
|
||||
for device in self:
|
||||
res = device._get_interface().test()
|
||||
if res and "error" not in res and device.state == "not_ready":
|
||||
@@ -78,12 +72,14 @@ class CubiscanDevice(models.Model):
|
||||
elif res and "error" in res and device.state == "ready":
|
||||
device.state = "not_ready"
|
||||
|
||||
@api.multi
|
||||
def get_measure(self):
|
||||
"""Return a measure from the Cubiscan device"""
|
||||
self.ensure_one()
|
||||
if self.state != "ready":
|
||||
raise UserError(
|
||||
"Device is not ready. Please use the 'Test'"
|
||||
" button before using the device."
|
||||
_(
|
||||
"Device is not ready. Please use the 'Test'"
|
||||
" button before using the device."
|
||||
)
|
||||
)
|
||||
return self._get_interface().measure()
|
||||
|
||||
@@ -14,7 +14,8 @@ class StockWarehouse(models.Model):
|
||||
|
||||
class ProductPackaging(models.Model):
|
||||
_inherit = "product.packaging"
|
||||
# FIXME: Not sure this is still the best place for this constraint
|
||||
# FIXME: move this constraint in product_packaging_type
|
||||
# https://github.com/OCA/product-attribute/tree/13.0/product_packaging_type
|
||||
_sql_constraints = [
|
||||
(
|
||||
"product_packaging_type_unique",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Copyright 2019 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
# from cubiscan.cubiscan import CubiScan
|
||||
# from mock import patch
|
||||
from cubiscan.cubiscan import CubiScan
|
||||
from mock import patch
|
||||
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
@@ -66,63 +67,56 @@ class TestCubiscanWizard(SavepointCase):
|
||||
PackType.cron_check_create_required_packaging()
|
||||
|
||||
def test_product_onchange(self):
|
||||
return
|
||||
# self.wizard.product_id = self.product_1.id
|
||||
self.wizard.product_id = self.product_1.id
|
||||
|
||||
# self.assertEqual(len(self.wizard.line_ids), 0)
|
||||
# self.wizard.onchange_product_id()
|
||||
# self.assertEqual(len(self.wizard.line_ids), 5)
|
||||
self.assertEqual(len(self.wizard.line_ids), 0)
|
||||
self.wizard.onchange_product_id()
|
||||
self.assertEqual(len(self.wizard.line_ids), 6)
|
||||
|
||||
def test_product_onchange_barcode(self):
|
||||
return
|
||||
# self.assertFalse(self.wizard.product_id)
|
||||
# self.assertFalse(self.wizard.line_ids)
|
||||
self.assertFalse(self.wizard.product_id)
|
||||
self.assertFalse(self.wizard.line_ids)
|
||||
|
||||
# self.wizard.on_barcode_scanned('424242')
|
||||
self.wizard.on_barcode_scanned("424242")
|
||||
|
||||
# self.assertEqual(self.wizard.product_id, self.product_1)
|
||||
# self.assertEqual(len(self.wizard.line_ids), 5)
|
||||
self.assertEqual(self.wizard.product_id, self.product_1)
|
||||
self.assertEqual(len(self.wizard.line_ids), 6)
|
||||
|
||||
def test_cubiscan_measures(self):
|
||||
return
|
||||
# self.wizard.product_id = self.product_1.id
|
||||
# self.wizard.onchange_product_id()
|
||||
self.wizard.product_id = self.product_1.id
|
||||
self.wizard.onchange_product_id()
|
||||
|
||||
# with patch.object(CubiScan, '_make_request') as request:
|
||||
# for idx, line in enumerate(self.wizard.line_ids):
|
||||
# request.return_value = TestCubiscanWizard.get_measure_result(
|
||||
# 2 ** idx, 1, 1, 2 ** idx
|
||||
# )
|
||||
# line.cubiscan_measure()
|
||||
# self.assertEqual(
|
||||
# line.read(
|
||||
# ['length', 'width', 'height', 'max_weight', 'volume']
|
||||
# )[0],
|
||||
# {
|
||||
# 'id': line.id,
|
||||
# 'length': (2 ** idx) * 1000,
|
||||
# 'width': 1000,
|
||||
# 'height': 1000,
|
||||
# 'max_weight': 2.0 ** idx,
|
||||
# 'volume': 2.0 ** idx,
|
||||
# },
|
||||
# )
|
||||
with patch.object(CubiScan, "_make_request") as request:
|
||||
for idx, line in enumerate(self.wizard.line_ids):
|
||||
request.return_value = TestCubiscanWizard.get_measure_result(
|
||||
2 ** idx, 1, 1, 2 ** idx
|
||||
)
|
||||
line.cubiscan_measure()
|
||||
self.assertEqual(
|
||||
line.read(["lngth", "width", "height", "max_weight", "volume"])[0],
|
||||
{
|
||||
"id": line.id,
|
||||
"lngth": (2 ** idx) * 1000,
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"max_weight": 2.0 ** idx,
|
||||
"volume": 2.0 ** idx,
|
||||
},
|
||||
)
|
||||
|
||||
# self.wizard.action_save()
|
||||
self.wizard.action_save()
|
||||
|
||||
# packagings = self.product_1.packaging_ids
|
||||
# self.assertEqual(len(packagings), 5)
|
||||
# for idx, packaging in enumerate(packagings):
|
||||
# self.assertEqual(
|
||||
# packaging.read(
|
||||
# ['length', 'width', 'height', 'max_weight', 'volume']
|
||||
# )[0],
|
||||
# {
|
||||
# 'id': packaging.id,
|
||||
# 'length': (2 ** idx) * 1000,
|
||||
# 'width': 1000,
|
||||
# 'height': 1000,
|
||||
# 'max_weight': 2.0 ** idx,
|
||||
# 'volume': 2.0 ** idx,
|
||||
# },
|
||||
# )
|
||||
packagings = self.product_1.packaging_ids.sorted()
|
||||
self.assertEqual(len(packagings), 6)
|
||||
for idx, packaging in enumerate(packagings):
|
||||
self.assertEqual(
|
||||
packaging.read(["lngth", "width", "height", "max_weight", "volume"])[0],
|
||||
{
|
||||
"id": packaging.id,
|
||||
"lngth": (2 ** idx) * 1000,
|
||||
"width": 1000,
|
||||
"height": 1000,
|
||||
"max_weight": 2.0 ** idx,
|
||||
"volume": 2.0 ** idx,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
<record id="action_cubiscan_device_form" model="ir.actions.act_window">
|
||||
<field name="name">CubiScan Devices</field>
|
||||
<field name="res_model">cubiscan.device</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class CubiscanWizard(models.TransientModel):
|
||||
"""This wizard is used to show a screen showing Cubiscan information
|
||||
|
||||
It is opened in a headless view (no breadcrumb, no menus, fullscreen).
|
||||
"""
|
||||
|
||||
_name = "cubiscan.wizard"
|
||||
_inherit = "barcodes.barcode_events_mixin"
|
||||
_description = "Cubiscan Wizard"
|
||||
@@ -33,7 +38,7 @@ class CubiscanWizard(models.TransientModel):
|
||||
"name": pack_type.name,
|
||||
"qty": 0,
|
||||
"max_weight": 0,
|
||||
"length": 0,
|
||||
"lngth": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"barcode": False,
|
||||
@@ -44,7 +49,7 @@ class CubiscanWizard(models.TransientModel):
|
||||
{
|
||||
"qty": pack.qty,
|
||||
"max_weight": pack.max_weight,
|
||||
"length": pack.length,
|
||||
"lngth": pack.lngth,
|
||||
"width": pack.width,
|
||||
"height": pack.height,
|
||||
"barcode": pack.barcode,
|
||||
@@ -58,7 +63,6 @@ class CubiscanWizard(models.TransientModel):
|
||||
else:
|
||||
self.line_ids = [(5, 0, 0)]
|
||||
|
||||
@api.multi
|
||||
def action_reopen_fullscreen(self):
|
||||
# Action to reopen wizard in fullscreen (e.g. after page refresh)
|
||||
self.ensure_one()
|
||||
@@ -75,14 +79,12 @@ class CubiscanWizard(models.TransientModel):
|
||||
"target": "new",
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def on_barcode_scanned(self, barcode):
|
||||
self.ensure_one()
|
||||
prod = self.env["product.product"].search([("barcode", "=", barcode)])
|
||||
self.product_id = prod
|
||||
self.onchange_product_id()
|
||||
|
||||
@api.multi
|
||||
def action_save(self):
|
||||
self.ensure_one()
|
||||
actions = []
|
||||
@@ -91,7 +93,7 @@ class CubiscanWizard(models.TransientModel):
|
||||
"name": line.name,
|
||||
"qty": line.qty,
|
||||
"max_weight": line.max_weight,
|
||||
"length": line.length,
|
||||
"lngth": line.lngth,
|
||||
"width": line.width,
|
||||
"height": line.height,
|
||||
"barcode": line.barcode,
|
||||
@@ -103,8 +105,9 @@ class CubiscanWizard(models.TransientModel):
|
||||
else:
|
||||
actions.append((0, 0, vals))
|
||||
self.product_id.packaging_ids = actions
|
||||
# reload lines
|
||||
self.onchange_product_id()
|
||||
|
||||
@api.multi
|
||||
def action_close(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref("stock_cubiscan.action_cubiscan_device_form").read()[0]
|
||||
@@ -134,7 +137,9 @@ class CubiscanWizardLine(models.TransientModel):
|
||||
name = fields.Char("Packaging", readonly=True)
|
||||
qty = fields.Float("Quantity")
|
||||
max_weight = fields.Float("Weight (kg)", readonly=True)
|
||||
length = fields.Integer("Length (mm)", readonly=True)
|
||||
# this is not a typo:
|
||||
# https://github.com/odoo/odoo/issues/41353#issuecomment-568037415
|
||||
lngth = fields.Integer("Length (mm)", readonly=True)
|
||||
width = fields.Integer("Width (mm)", readonly=True)
|
||||
height = fields.Integer("Height (mm)", readonly=True)
|
||||
volume = fields.Float(
|
||||
@@ -151,20 +156,20 @@ class CubiscanWizardLine(models.TransientModel):
|
||||
)
|
||||
required = fields.Boolean(related="packaging_type_id.required", readonly=True)
|
||||
|
||||
@api.depends("length", "width", "height")
|
||||
@api.depends("lngth", "width", "height")
|
||||
def _compute_volume(self):
|
||||
for line in self:
|
||||
line.volume = (line.length * line.width * line.height) / 1000.0 ** 3
|
||||
line.volume = (line.lngth * line.width * line.height) / 1000.0 ** 3
|
||||
|
||||
@api.multi
|
||||
def cubiscan_measure(self):
|
||||
self.ensure_one()
|
||||
measures = self.wizard_id.device_id.get_measure()
|
||||
# measures are a tuple of 2 slots (measure, precision error),
|
||||
# we only care about the measure for now
|
||||
measures = {
|
||||
k: (v[0] if k in ["length", "width", "height", "weight"] else False)
|
||||
for k, v in measures.items()
|
||||
"lngth": int(measures["length"][0] * 1000),
|
||||
"width": int(measures["width"][0] * 1000),
|
||||
"height": int(measures["height"][0] * 1000),
|
||||
"max_weight": measures["weight"][0],
|
||||
}
|
||||
weight = measures.pop("weight")
|
||||
measures = {k: int(v * 1000) for k, v in measures.items()}
|
||||
measures["max_weight"] = weight
|
||||
self.write(measures)
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
</header>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" nolabel="1" options="{'no_open': True, 'create': False, 'create_edit': False}" />
|
||||
<label for="device_id" />
|
||||
<field name="device_id" nolabel="1" options="{'no_open': True, 'no_create_edit': True}" />
|
||||
<label for="product_id" />
|
||||
<field name="product_id" nolabel="1" options="{'no_open': True, 'no_create_edit': True}" />
|
||||
<field name="_barcode_scanned" widget="barcode_handler" invisible="1" />
|
||||
</group>
|
||||
<group/>
|
||||
@@ -23,7 +26,7 @@
|
||||
<field name="name" />
|
||||
<field name="qty" />
|
||||
<field name="max_weight" options="{'bg_color': 'lightcoral: max_weight == 0.0 and required'}" />
|
||||
<field name="length" options="{'bg_color': 'lightcoral: length == 0.0 and required'}" />
|
||||
<field name="lngth" options="{'bg_color': 'lightcoral: lngth == 0.0 and required'}" />
|
||||
<field name="width" options="{'bg_color': 'lightcoral: width == 0.0 and required'}" />
|
||||
<field name="height" options="{'bg_color': 'lightcoral: height == 0.0 and required'}" />
|
||||
<field name="volume" options="{'bg_color': 'lightcoral: volume == 0.0 and required'}" />
|
||||
|
||||
Reference in New Issue
Block a user