[ADD] stock_measuring_device

This is a rework of the stock_cubiscan module to allow supporting
different kind of measuring devices, with different communication
protocols. We use components from OCA/connector to abstract the
implementation of the backend (some devices are synchronous, others are
asynchronous...).

Others PR to come for stock_measuring_device_cubiscan and
stock_measuring_device_zippcube.
This commit is contained in:
Alexandre Fayolle
2021-04-15 18:08:36 +02:00
committed by Hai Lang
parent b06f188927
commit 02ec5184d4
28 changed files with 1649 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
======================
Stock Measuring Device
======================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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/13.0/stock_measuring_device
: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-13-0/stock-logistics-warehouse-13-0-stock_measuring_device
: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/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
Different manufacturers produce devices which are able to measure and weigh
packages and parcels. Each brand has a different communication protocol. This
module provides an framework to interface such devices with Odoo.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Installation
============
This module by itself does not do anything.
You will need to install a module implementing the communication with your
device. Look for modules with a name starting with stock_measuring_device.
Configuration
=============
The first step is to configure the Packaging Types (Pallet, Box, ...) in Inventory > Configuration > Product Packaging Types.
Configure the measuring device in Inventory > Configuration > Measuring
Devices, don't forget to set the device type, and any other additional
parameters.
Usage
=====
Use the "Wizard" button on a Measuring Device to open the screen and take
measurements.
Known issues / Roadmap
======================
* The UI could get some improvements
* Being able to open the measurement screen from a product would be nice
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_measuring_device%0Aversion:%2013.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
~~~~~~~~~~~~
* Patrick Tombez <patrick.tombez@camptocamp.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Carlos Serra Toro <carlos.serra@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.
.. |maintainer-gurneyalex| image:: https://github.com/gurneyalex.png?size=40px
:target: https://github.com/gurneyalex
:alt: gurneyalex
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-gurneyalex|
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_measuring_device>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,5 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import components
from . import models
from . import wizard

View File

@@ -0,0 +1,34 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
{
"name": "Stock Measuring Device",
"summary": "Implement a common interface for measuring and weighing devices",
"version": "13.0.1.0.0",
"category": "Warehouse",
"author": "Camptocamp, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": [
"component",
"barcodes",
"stock",
"web_tree_dynamic_colored_field",
"product_packaging_dimension",
"product_packaging_type_required",
"product_dimension",
# For the pop-up message to tell the user to refresh.
"web_notify",
"web_ir_actions_act_view_reload",
],
"data": [
"security/ir.model.access.csv",
"data/uom.xml",
"views/assets.xml",
"views/measuring_device_view.xml",
"wizard/measuring_wizard.xml",
"views/menu.xml",
],
"website": "https://github.com/OCA/stock-logistics-warehouse",
"installable": True,
"development_status": "Alpha",
"maintainers": ["gurneyalex"],
}

View File

@@ -0,0 +1,3 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import measuring_device_component

View File

@@ -0,0 +1,46 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import logging
from odoo.addons.component.core import AbstractComponent
_logger = logging.getLogger(__name__)
class MeasuringDevice(AbstractComponent):
_name = "measuring.device.base"
_collection = "measuring.device"
def test_device(self):
"""Test that the device is properly configured.
Override to e.g. test the connection parameter or send a test command.
:return: True on success, False otherwise"""
return True
def preprocess_measures(self, measures):
"""perform required parsing, key mapping and possible unit conversion
:param measures: a dictionary passed to _update_packaging_measures
:return: a dictionary containing values that will be written on the
wizard line
"""
return measures
def post_update_packaging_measures(self, measures, packaging, wizard_line):
"""hook called after the update of the packaging or wizard line update.
This method can be called to send notifications for instance.
:return: None"""
pass
def get_measure(self):
"""Get a measure from the device
the implementation must communicate with the device to trigger a
measure. If this can be done synchronously, call
_update_packaging_measures() to get the update"""
raise NotImplementedError()

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="product_uom_mm" model="uom.uom">
<field name="category_id" ref="uom.uom_categ_length" />
<field name="name">mm</field>
<field name="factor" eval="1000" />
<field name="uom_type">smaller</field>
</record>
</odoo>

View File

@@ -0,0 +1,305 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_measuring_device
#
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_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard___barcode_scanned
msgid "Barcode Scanned"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Cancel Scan"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Close"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__create_uid
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__create_uid
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__create_uid
msgid "Created by"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__create_date
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__create_date
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__create_date
msgid "Created on"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__device_id
msgid "Device"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__device_type
msgid "Device Type"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__display_name
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__display_name
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__display_name
msgid "Display Name"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Fullscreen"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__barcode
msgid "GTIN"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__height
msgid "Height (mm)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__id
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__id
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__id
msgid "ID"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__is_unit_line
msgid "Is Unit Line"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device____last_update
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard____last_update
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line____last_update
msgid "Last Modified on"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__write_uid
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__write_uid
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__write_uid
msgid "Last Updated by"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__write_date
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__write_date
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__write_date
msgid "Last Updated on"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__lngth
msgid "Length (mm)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__line_ids
msgid "Line"
msgstr ""
#. module: stock_measuring_device
#: code:addons/stock_measuring_device/models/measuring_device.py:0
#, python-format
msgid "Measurement Wizard"
msgstr ""
#. module: stock_measuring_device
#: code:addons/stock_measuring_device/wizard/measuring_wizard_line.py:0
#, python-format
msgid "Measurement machine already in use."
msgstr ""
#. module: stock_measuring_device
#: model:ir.actions.act_window,name:stock_measuring_device.action_measuring_device_form
#: model:ir.model.fields,field_description:stock_measuring_device.field_stock_warehouse__measuring_device_ids
#: model:ir.ui.menu,name:stock_measuring_device.menu_measuring_device
msgid "Measuring Devices"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model,name:stock_measuring_device.model_measuring_device
msgid "Measuring and Weighing Device"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_product_packaging__measuring_device_id
msgid "Measuring device which will scan the package"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__name
msgid "Name"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields.selection,name:stock_measuring_device.selection__measuring_device__state__not_ready
msgid "Not Ready"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__name
msgid "Packaging"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__packaging_id
msgid "Packaging (rel)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__packaging_type_id
msgid "Packaging Type"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard__product_id
msgid "Product"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model,name:stock_measuring_device.model_product_packaging
msgid "Product Packaging"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__qty
msgid "Quantity"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields.selection,name:stock_measuring_device.selection__measuring_device__state__ready
msgid "Ready"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Refresh"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__required
msgid "Required"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Save"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_wizard
msgid "Scan"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__scan_requested
msgid "Scan Requested"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__sequence
msgid "Sequence"
msgstr ""
#. module: stock_measuring_device
#: code:addons/stock_measuring_device/models/product_packaging.py:0
#, python-format
msgid ""
"Several packagings ({}) found to update by device {}. Will update the first:"
" {}"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__state
msgid "State"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,help:stock_measuring_device.field_product_packaging__measuring_device_id
msgid ""
"Technical field set when an operator uses the device to scan this package"
msgstr ""
#. module: stock_measuring_device
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_device_form
msgid "Test Device"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.constraint,message:stock_measuring_device.constraint_measuring_device_name_uniq
msgid "The name of the measuring/weighing device must be unique."
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,help:stock_measuring_device.field_measuring_device__device_type
msgid ""
"The type of device (e.g. zippcube, cubiscan...) depending on which module "
"are installed."
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,help:stock_measuring_device.field_measuring_wizard___barcode_scanned
msgid "Value of the last barcode scanned."
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__volume
msgid "Volume (m³)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model,name:stock_measuring_device.model_stock_warehouse
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_device__warehouse_id
msgid "Warehouse"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__max_weight
msgid "Weight (kg)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__width
msgid "Width (mm)"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model.fields,field_description:stock_measuring_device.field_measuring_wizard_line__wizard_id
#: model_terms:ir.ui.view,arch_db:stock_measuring_device.view_measuring_device_form
msgid "Wizard"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model,name:stock_measuring_device.model_measuring_wizard
msgid "measuring Wizard"
msgstr ""
#. module: stock_measuring_device
#: model:ir.model,name:stock_measuring_device.model_measuring_wizard_line
msgid "measuring Wizard Line"
msgstr ""
#. module: stock_measuring_device
#: model:uom.uom,name:stock_measuring_device.product_uom_mm
msgid "mm"
msgstr ""

View File

@@ -0,0 +1,5 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import measuring_device
from . import product_packaging
from . import stock_warehouse

View File

@@ -0,0 +1,104 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import logging
from odoo import _, fields, models
_logger = logging.getLogger(__name__)
class MeasuringDevice(models.Model):
_name = "measuring.device"
_inherit = "collection.base"
_description = "Measuring and Weighing Device"
_order = "warehouse_id, name"
name = fields.Char("Name", required=True)
warehouse_id = fields.Many2one("stock.warehouse", "Warehouse", required=True)
device_type = fields.Selection(
selection=[],
help="The type of device (e.g. zippcube, cubiscan...) "
"depending on which module are installed.",
)
state = fields.Selection(
[("not_ready", "Not Ready"), ("ready", "Ready")],
default="not_ready",
readonly=True,
copy=False,
)
_sql_constraints = [
(
"name_uniq",
"unique (name)",
"The name of the measuring/weighing device must be unique.",
),
]
def _get_measuring_device(self):
with self.work_on(self._name) as work_ctx:
return work_ctx.component(usage=self.device_type)
def open_wizard(self):
return {
"name": _("Measurement Wizard"),
"res_model": "measuring.wizard",
"type": "ir.actions.act_window",
"view_id": False,
"view_mode": "form",
"context": {"default_device_id": self.id},
"target": "fullscreen",
"flags": {
"withControlPanel": False,
"form_view_initial_mode": "edit",
"no_breadcrumbs": True,
},
}
def _is_being_used(self):
self.ensure_one()
return bool(
self.env["product.packaging"].search_count(
[("measuring_device_id", "=", self.id)]
)
)
def _update_packaging_measures(self, measures):
self.ensure_one()
measures = self._get_measuring_device().preprocess_measures(measures)
line_domain = [
("wizard_id.device_id", "=", self.id),
("scan_requested", "=", True),
]
packaging = self.env["product.packaging"]._measuring_device_find_assigned(self)
if packaging:
line_domain += [("packaging_id", "=", packaging.id)]
else:
line_domain += [
("packaging_id", "=", False),
("is_unit_line", "=", True),
]
wizard_line = self.env["measuring.wizard.line"].search(
line_domain, order="write_date DESC", limit=1,
)
if not wizard_line:
_logger.warning("No wizard line found for this measure.")
packaging.write(measures)
else:
measures.update({"scan_requested": False})
wizard_line.write(measures)
self._get_measuring_device().post_update_packaging_measures(
measures, packaging, wizard_line
)
return packaging
def test_device(self):
for rec in self:
success = rec._get_measuring_device().test_device()
if success and rec.state == "not_ready":
rec.state = "ready"
elif not success and rec.state == "ready":
rec.state = "not_ready"

View File

@@ -0,0 +1,49 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import logging
from odoo import _, fields, models
_logger = logging.getLogger(__name__)
class ProductPackaging(models.Model):
_inherit = "product.packaging"
measuring_device_id = fields.Many2one(
"measuring.device",
copy=False,
string="Measuring device which will scan the package",
help="Technical field set when an operator uses the device "
"to scan this package",
)
def _measuring_device_assign(self, device):
"""Assign the measuring device to the current product packaging"""
# self can be an empty recordset, for the unit line which updates the
# product info
if self:
self.measuring_device_id = device
def _measuring_device_release(self):
"""Free the measuring device from the current product packaging"""
# self can be an empty recordset, for the unit line which updates the
# product info
if self:
self.measuring_device_id = False
def _measuring_device_find_assigned(self, device):
"""search packaging being assigned to the specified device"""
packaging = self.search(
[("measuring_device_id", "=", device.id)], order="write_date DESC", limit=2
)
if len(packaging) > 1:
warning_msg = _(
"Several packagings ({}) found to update by "
"device {}. Will update the first: {}".format(
packaging, self.name, packaging[0]
)
)
_logger.warning(warning_msg)
packaging = packaging[0]
return packaging

View File

@@ -0,0 +1,12 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class StockWarehouse(models.Model):
_inherit = "stock.warehouse"
measuring_device_ids = fields.One2many(
"measuring.device", "warehouse_id", string="Measuring Devices"
)

View File

@@ -0,0 +1,5 @@
The first step is to configure the Packaging Types (Pallet, Box, ...) in Inventory > Configuration > Product Packaging Types.
Configure the measuring device in Inventory > Configuration > Measuring
Devices, don't forget to set the device type, and any other additional
parameters.

View File

@@ -0,0 +1,3 @@
* Patrick Tombez <patrick.tombez@camptocamp.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Carlos Serra Toro <carlos.serra@camptocamp.com>

View File

@@ -0,0 +1,3 @@
Different manufacturers produce devices which are able to measure and weigh
packages and parcels. Each brand has a different communication protocol. This
module provides an framework to interface such devices with Odoo.

View File

@@ -0,0 +1,4 @@
This module by itself does not do anything.
You will need to install a module implementing the communication with your
device. Look for modules with a name starting with stock_measuring_device.

View File

@@ -0,0 +1,2 @@
* The UI could get some improvements
* Being able to open the measurement screen from a product would be nice

View File

@@ -0,0 +1,2 @@
Use the "Wizard" button on a Measuring Device to open the screen and take
measurements.

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_measuring_device_inventory_manager,measuring.device.inventory.manager,stock_measuring_device.model_measuring_device,stock.group_stock_manager,1,1,1,1
access_measuring_device_inventory_user,measuring.device.inventory.user,stock_measuring_device.model_measuring_device,stock.group_stock_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_measuring_device_inventory_manager measuring.device.inventory.manager stock_measuring_device.model_measuring_device stock.group_stock_manager 1 1 1 1
3 access_measuring_device_inventory_user measuring.device.inventory.user stock_measuring_device.model_measuring_device stock.group_stock_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,460 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Stock Measuring Device</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="stock-measuring-device">
<h1 class="title">Stock Measuring Device</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/13.0/stock_measuring_device"><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-13-0/stock-logistics-warehouse-13-0-stock_measuring_device"><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/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>Different manufacturers produce devices which are able to measure and weigh
packages and parcels. Each brand has a different communication protocol. This
module provides an framework to interface such devices with Odoo.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id3">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id4">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id5">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id6">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id7">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id8">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>This module by itself does not do anything.</p>
<p>You will need to install a module implementing the communication with your
device. Look for modules with a name starting with stock_measuring_device.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
<p>The first step is to configure the Packaging Types (Pallet, Box, …) in Inventory &gt; Configuration &gt; Product Packaging Types.</p>
<p>Configure the measuring device in Inventory &gt; Configuration &gt; Measuring
Devices, dont forget to set the device type, and any other additional
parameters.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id3">Usage</a></h1>
<p>Use the “Wizard” button on a Measuring Device to open the screen and take
measurements.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id4">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>The UI could get some improvements</li>
<li>Being able to open the measurement screen from a product would be nice</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id5">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_measuring_device%0Aversion:%2013.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="#id6">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id7">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id8">Contributors</a></h2>
<ul class="simple">
<li>Patrick Tombez &lt;<a class="reference external" href="mailto:patrick.tombez&#64;camptocamp.com">patrick.tombez&#64;camptocamp.com</a>&gt;</li>
<li>Alexandre Fayolle &lt;<a class="reference external" href="mailto:alexandre.fayolle&#64;camptocamp.com">alexandre.fayolle&#64;camptocamp.com</a>&gt;</li>
<li>Carlos Serra Toro &lt;<a class="reference external" href="mailto:carlos.serra&#64;camptocamp.com">carlos.serra&#64;camptocamp.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id9">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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/gurneyalex"><img alt="gurneyalex" src="https://github.com/gurneyalex.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_measuring_device">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>

View File

@@ -0,0 +1,33 @@
.o_web_client.o_fullscreen {
.o_form_view.measuring_wizard {
font-size: 16px;
@include media-breakpoint-up(x1) {
font-size: 18px;
}
.btn {
font-size: 1em;
padding: 1em;
margin: 0 5px;
}
.o_data_cell:not(.o_list_button) {
padding: 0.75em;
font-size: 1.5em;
margin: 0 5px;
}
.table-responsive {
overflow: hidden;
}
.o_field_many2one input.o_input {
font-size: 1.5em;
}
.o_form_statusbar {
display: none;
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<odoo>
<template
id="stock_measuring_device_assets"
name="stock_measuring_device assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<link
rel="stylesheet"
type="text/scss"
href="/stock_measuring_device/static/src/scss/measuring_wizard.scss"
/>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" ?>
<odoo>
<record model="ir.ui.view" id="view_measuring_device_form">
<field name="name">measuring.device.form</field>
<field name="model">measuring.device</field>
<field name="arch" type="xml">
<form>
<header>
<button
type="object"
name="open_wizard"
string="Wizard"
class="oe_highlight"
/>
<button
type="object"
name="test_device"
string="Test Device"
attrs="{'invisible': [('id','=',False)]}"
/>
<field name="state" widget="statusbar" />
</header>
<sheet>
<group>
<group name="device">
<field name="name" />
<field name="warehouse_id" />
<field name="device_type" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_measuring_device_tree">
<field name="name">measuring.device.tree</field>
<field name="model">measuring.device</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="warehouse_id" />
<field name="device_type" />
</tree>
</field>
</record>
<record id="action_measuring_device_form" model="ir.actions.act_window">
<field name="name">Measuring Devices</field>
<field name="res_model">measuring.device</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" ?>
<odoo>
<menuitem
id="menu_measuring_device"
action="action_measuring_device_form"
parent="stock.menu_warehouse_config"
sequence="100"
/>
</odoo>

View File

@@ -0,0 +1,4 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import measuring_wizard
from . import measuring_wizard_line

View File

@@ -0,0 +1,178 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import api, fields, models
class MeasuringWizard(models.TransientModel):
_name = "measuring.wizard"
_inherit = "barcodes.barcode_events_mixin"
_description = "measuring Wizard"
_rec_name = "device_id"
product_id = fields.Many2one("product.product", domain=[("type", "=", "product")])
line_ids = fields.One2many("measuring.wizard.line", "wizard_id")
device_id = fields.Many2one("measuring.device", readonly=True)
@api.onchange("product_id")
def onchange_product_id(self):
if self.product_id:
to_create = []
to_create += self._prepare_unit_line()
to_create += self._prepare_packaging_lines()
recs = self.env["measuring.wizard.line"].create(to_create)
self.line_ids = recs
else:
self.line_ids = [(5, 0, 0)]
def _prepare_unit_line(self):
vals = {
"wizard_id": self.id,
"sequence": 0,
"name": "Unit",
"qty": 1,
"max_weight": self.product_id.weight,
"lngth": self.product_id.product_length,
"width": self.product_id.product_width,
"height": self.product_id.product_height,
"is_unit_line": True,
}
product_dimension_uom = self.product_id.dimensional_uom_id
mm_uom = self.env.ref("stock_measuring_device.product_uom_mm")
if mm_uom != product_dimension_uom:
vals.update(
{
"lngth": product_dimension_uom._compute_quantity(
self.product_id.product_length, mm_uom
),
"width": product_dimension_uom._compute_quantity(
self.product_id.product_width, mm_uom
),
"height": product_dimension_uom._compute_quantity(
self.product_id.product_height, mm_uom
),
}
)
return [vals]
def _prepare_packaging_lines(self):
vals_list = []
product_packaging = self.env["product.packaging"]
packaging_types = self.env["product.packaging.type"].search([])
for seq, pack_type in enumerate(packaging_types):
pack = product_packaging.search(
[
("product_id", "=", self.product_id.id),
("packaging_type_id", "=", pack_type.id),
],
limit=1,
)
vals = {
"wizard_id": self.id,
"sequence": seq + 1,
"name": pack_type.name,
"qty": 0,
"max_weight": 0,
"lngth": 0,
"width": 0,
"height": 0,
"barcode": False,
"packaging_type_id": pack_type.id,
}
if pack:
vals.update(
{
"qty": pack.qty,
"max_weight": pack.max_weight,
"lngth": pack.lngth,
"width": pack.width,
"height": pack.height,
"barcode": pack.barcode,
"packaging_id": pack.id,
"packaging_type_id": pack_type.id,
}
)
vals_list.append(vals)
return vals_list
def action_reopen_fullscreen(self):
self.ensure_one()
res = self.device_id.open_wizard()
res["res_id"] = self.id
return res
def on_barcode_scanned(self, barcode):
self.ensure_one()
prod = self.env["product.product"].search([("barcode", "=", barcode)], limit=1)
self.product_id = prod
def action_save(self):
self.ensure_one()
product_vals = {}
packaging_ids_list = []
for line in self.line_ids:
packaging_type = line.packaging_type_id
if packaging_type:
# Handle lines with packaging
vals = {
"name": line.name,
"qty": line.qty,
"max_weight": line.max_weight,
"lngth": line.lngth,
"width": line.width,
"height": line.height,
"barcode": line.barcode,
"packaging_type_id": line.packaging_type_id.id,
}
pack = line.packaging_id
if pack:
packaging_ids_list.append((1, pack.id, vals))
else:
packaging_ids_list.append((0, 0, vals))
else:
# Handle unit line
mm_uom = self.env.ref("stock_measuring_device.product_uom_mm")
product_vals.update(
{
"product_length": line.lngth,
"product_width": line.width,
"product_height": line.height,
"dimensional_uom_id": mm_uom.id,
"weight": line.max_weight,
}
)
product_vals.update({"packaging_ids": packaging_ids_list})
self.product_id.write(product_vals)
# Call onchange to update volume on product.product
self.product_id.onchange_calculate_volume()
# reload lines
self.onchange_product_id()
def action_close(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"res_model": self.device_id._name,
"res_id": self.device_id.id,
"view_mode": "form",
"target": "main",
"flags": {"headless": False, "clear_breadcrumbs": True},
}
def reload(self):
return {
"type": "ir.actions.act_view_reload",
}
def _notify(self, message):
"""Show a gentle notification on the wizard
We can't use the user set in the current environment because the user
that attends the screen (that opened the wizard, thus created it) may
be not the same than the one (artificial user) that scans and submits
the data, e.g. by using an api call via a controller. We have to send
this original user in the environment because notify_warning checks
that you only notify a user which is the same than the one set in
the environment.
"""
self.ensure_one()
self.create_uid.with_user(self.create_uid.id).notify_warning(message=message)

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_measuring_wizard" model="ir.ui.view">
<field name="name">measuring.wizard.form</field>
<field name="model">measuring.wizard</field>
<field name="arch" type="xml">
<form class="measuring_wizard">
<header>
<button
name="action_reopen_fullscreen"
string="Fullscreen"
type="object"
/>
</header>
<group>
<group name="col1">
<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"
/>
<button
name="reload"
type="object"
string="Refresh"
icon="fa-refresh"
/>
</group>
<group name="col2" />
</group>
<separator />
<field name="line_ids">
<tree
editable="bottom"
create="0"
delete="0"
decoration-warning="scan_requested is True"
>
<field name="scan_requested" invisible="1" />
<field name="sequence" invisible="1" />
<field name="required" invisible="1" />
<field name="name" />
<field name="qty" />
<field
name="max_weight"
options="{'bg_color': 'lightcoral: max_weight == 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'}"
/>
<button
name="measuring_select_for_measure"
type="object"
string="Scan"
class="btn btn-warning"
/>
<button
name="measuring_select_for_measure_cancel"
type="object"
string="Cancel Scan"
class="btn btn-danger"
attrs="{'invisible': [('scan_requested', '!=', True)]}"
/>
<field name="barcode" />
</tree>
</field>
<footer>
<button
name="action_save"
type="object"
icon="fa-check"
class="btn btn-primary"
string="Save"
/>
<button
name="action_close"
type="object"
icon="fa-times"
class="btn btn-danger"
string="Close"
/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,74 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import _, api, fields, models
class MeasuringWizardLine(models.TransientModel):
_name = "measuring.wizard.line"
_description = "measuring Wizard Line"
_order = "sequence"
scan_requested = fields.Boolean()
wizard_id = fields.Many2one("measuring.wizard")
sequence = fields.Integer()
name = fields.Char("Packaging", readonly=True)
qty = fields.Float("Quantity")
max_weight = fields.Float("Weight (kg)", 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(
"Volume (m³)",
digits=(8, 4),
compute="_compute_volume",
readonly=True,
store=False,
)
barcode = fields.Char("GTIN")
packaging_id = fields.Many2one(
"product.packaging", string="Packaging (rel)", readonly=True
)
packaging_type_id = fields.Many2one("product.packaging.type", readonly=True)
is_unit_line = fields.Boolean(readonly=True)
required = fields.Boolean(related="packaging_type_id.required", readonly=True)
@api.depends("lngth", "width", "height")
def _compute_volume(self):
for line in self:
line.volume = (line.lngth * line.width * line.height) / 1000.0 ** 3
def measuring_select_for_measure(self):
"""Current line has been selected for measurement
This implies that the device is acquired and locked,
and the packaging is assigned the device."""
self.ensure_one()
success = True
if not self.packaging_id and not self.is_unit_line:
pack_vals = {
"name": self.name,
"packaging_type_id": self.packaging_type_id.id,
"product_id": self.wizard_id.product_id.id,
}
pack = self.env["product.packaging"].create(pack_vals)
self.packaging_id = pack.id
if self.wizard_id.device_id._is_being_used():
self.wizard_id._notify(_("Measurement machine already in use."))
success = False
if success:
self.scan_requested = True
device = self.wizard_id.device_id
self.packaging_id._measuring_device_assign(device)
return success
def measuring_select_for_measure_cancel(self):
"""Current line has been de-selected for measurement
This implies that the packaging clears is assigned device."""
self.ensure_one()
self.scan_requested = False
self.packaging_id._measuring_device_release()
return True