From 47c69999fe74dfd745753ffff8b65c4ebc3a0f13 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 2 Feb 2023 14:08:17 +0100 Subject: [PATCH] [ADD] web_widget_remote_measure: New module TT38745 --- .../odoo/addons/web_widget_remote_measure | 1 + setup/web_widget_remote_measure/setup.py | 6 + web_widget_remote_measure/README.rst | 130 +++++ web_widget_remote_measure/__init__.py | 1 + web_widget_remote_measure/__manifest__.py | 25 + web_widget_remote_measure/models/__init__.py | 2 + .../models/remote_measure_device.py | 32 ++ web_widget_remote_measure/models/res_users.py | 12 + .../readme/CONFIGURE.rst | 5 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 5 + web_widget_remote_measure/readme/ROADMAP.rst | 10 + web_widget_remote_measure/readme/USAGE.rst | 16 + .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 9652 bytes .../static/description/index.html | 470 ++++++++++++++++++ ...remote_measure_device_kanban_widget.esm.js | 33 ++ .../src/js/remote_measure_widget.esm.js | 359 +++++++++++++ .../src/scss/remote_measure_widget.scss | 30 ++ .../static/src/xml/measure_device_status.xml | 15 + .../views/remote_measure_device_views.xml | 92 ++++ .../views/res_users_views.xml | 17 + 22 files changed, 1267 insertions(+) create mode 120000 setup/web_widget_remote_measure/odoo/addons/web_widget_remote_measure create mode 100644 setup/web_widget_remote_measure/setup.py create mode 100644 web_widget_remote_measure/README.rst create mode 100644 web_widget_remote_measure/__init__.py create mode 100644 web_widget_remote_measure/__manifest__.py create mode 100644 web_widget_remote_measure/models/__init__.py create mode 100644 web_widget_remote_measure/models/remote_measure_device.py create mode 100644 web_widget_remote_measure/models/res_users.py create mode 100644 web_widget_remote_measure/readme/CONFIGURE.rst create mode 100644 web_widget_remote_measure/readme/CONTRIBUTORS.rst create mode 100644 web_widget_remote_measure/readme/DESCRIPTION.rst create mode 100644 web_widget_remote_measure/readme/ROADMAP.rst create mode 100644 web_widget_remote_measure/readme/USAGE.rst create mode 100644 web_widget_remote_measure/security/ir.model.access.csv create mode 100644 web_widget_remote_measure/static/description/icon.png create mode 100644 web_widget_remote_measure/static/description/index.html create mode 100644 web_widget_remote_measure/static/src/js/remote_measure_device_kanban_widget.esm.js create mode 100644 web_widget_remote_measure/static/src/js/remote_measure_widget.esm.js create mode 100644 web_widget_remote_measure/static/src/scss/remote_measure_widget.scss create mode 100644 web_widget_remote_measure/static/src/xml/measure_device_status.xml create mode 100644 web_widget_remote_measure/views/remote_measure_device_views.xml create mode 100644 web_widget_remote_measure/views/res_users_views.xml diff --git a/setup/web_widget_remote_measure/odoo/addons/web_widget_remote_measure b/setup/web_widget_remote_measure/odoo/addons/web_widget_remote_measure new file mode 120000 index 000000000..8b24be629 --- /dev/null +++ b/setup/web_widget_remote_measure/odoo/addons/web_widget_remote_measure @@ -0,0 +1 @@ +../../../../web_widget_remote_measure \ No newline at end of file diff --git a/setup/web_widget_remote_measure/setup.py b/setup/web_widget_remote_measure/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_widget_remote_measure/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_widget_remote_measure/README.rst b/web_widget_remote_measure/README.rst new file mode 100644 index 000000000..67dc51ff2 --- /dev/null +++ b/web_widget_remote_measure/README.rst @@ -0,0 +1,130 @@ +============================ +Remote Measure Devices Input +============================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/15.0/web_widget_remote_measure + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_widget_remote_measure + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to input data from remote devices in your network. Currently, only +websockets devices are supported, but it can be extended for any protocol like +Webservices. + +Other modules can extend this one in order to use the widget. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure your remote devices: + +#. Go to *Settings > Technical > Devices > Remote devices* +#. Create a new one configuring the required info. +#. If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210 + +Usage +===== + +The remote device has to be in the users network so their web clients can reach them. + +In order to test a device you can: + +#. Go to *Settings > Technical > Devices > Remote devices* +#. In the Kanban view you'll wich devices can be reached as they'll have a green dot in + their card. +#. Go to one of those and click *Edit*. +#. You can start measuring from the remote device in the *Test measure* field. + +On the technical side, you can use the widget in your own `Float``. You'll need to +provide an uom field so records that aren't in that UoM don't measure from the device. + +.. code:: xml + + + +Known issues / Roadmap +====================== + +Current support: + +- Websockets connection +- F501 protocol on continuous message stream. + +But this is a commonground to add: + +- Other connection interfaces like Webservices APIs +- Other device protocols. +- Active device controls, la Tare, resets, etc. + +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 +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * 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 `__: + +|maintainer-chienandalu| + +This module is part of the `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_widget_remote_measure/__init__.py b/web_widget_remote_measure/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/web_widget_remote_measure/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/web_widget_remote_measure/__manifest__.py b/web_widget_remote_measure/__manifest__.py new file mode 100644 index 000000000..a90e6b4d6 --- /dev/null +++ b/web_widget_remote_measure/__manifest__.py @@ -0,0 +1,25 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Remote Measure Devices Input", + "summary": "Allows to connect to remote devices to record measures", + "version": "15.0.1.0.0", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/web", + "maintainers": ["chienandalu"], + "license": "AGPL-3", + "category": "Stock", + "depends": ["web", "uom"], + "data": [ + "views/remote_measure_device_views.xml", + "views/res_users_views.xml", + "security/ir.model.access.csv", + ], + "assets": { + "web.assets_backend": [ + "web_widget_remote_measure/static/src/**/*.js", + "web_widget_remote_measure/static/src/**/*.scss", + ], + "web.assets_qweb": ["web_widget_remote_measure/static/src/**/*.xml"], + }, +} diff --git a/web_widget_remote_measure/models/__init__.py b/web_widget_remote_measure/models/__init__.py new file mode 100644 index 000000000..e59cc94f8 --- /dev/null +++ b/web_widget_remote_measure/models/__init__.py @@ -0,0 +1,2 @@ +from . import remote_measure_device +from . import res_users diff --git a/web_widget_remote_measure/models/remote_measure_device.py b/web_widget_remote_measure/models/remote_measure_device.py new file mode 100644 index 000000000..9edfc10f0 --- /dev/null +++ b/web_widget_remote_measure/models/remote_measure_device.py @@ -0,0 +1,32 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class RemoteMeasureDevice(models.Model): + _name = "remote.measure.device" + _description = "Remote measure device" + + active = fields.Boolean(default=True) + name = fields.Char(required=True) + uom_id = fields.Many2one( + string="Unit of measure", + comodel_name="uom.uom", + required=True, + ) + uom_category_id = fields.Many2one(related="uom_id.category_id") + uom_factor = fields.Float(related="uom_id.factor") + protocol = fields.Selection( + selection=[("f501", "Scale F501")], + help="Operating protocol", + required=True, + ) + connection_mode = fields.Selection( + selection=[ + ("websockets", "Web Sockets"), + ("webservices", "Web Services"), + ], + required=True, + ) + host = fields.Char(required=True) + test_measure = fields.Float(default=0.0) diff --git a/web_widget_remote_measure/models/res_users.py b/web_widget_remote_measure/models/res_users.py new file mode 100644 index 000000000..8fb9864b7 --- /dev/null +++ b/web_widget_remote_measure/models/res_users.py @@ -0,0 +1,12 @@ +# Copyright 2023 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + remote_measure_device_id = fields.Many2one( + comodel_name="remote.measure.device", + help="Default remote measure device for this user", + ) diff --git a/web_widget_remote_measure/readme/CONFIGURE.rst b/web_widget_remote_measure/readme/CONFIGURE.rst new file mode 100644 index 000000000..3b58dd020 --- /dev/null +++ b/web_widget_remote_measure/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ +To configure your remote devices: + +#. Go to *Settings > Technical > Devices > Remote devices* +#. Create a new one configuring the required info. +#. If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210 diff --git a/web_widget_remote_measure/readme/CONTRIBUTORS.rst b/web_widget_remote_measure/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..94b6ba953 --- /dev/null +++ b/web_widget_remote_measure/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Tecnativa `_: + + * David Vidal diff --git a/web_widget_remote_measure/readme/DESCRIPTION.rst b/web_widget_remote_measure/readme/DESCRIPTION.rst new file mode 100644 index 000000000..a96bba15a --- /dev/null +++ b/web_widget_remote_measure/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module allows to input data from remote devices in your network. Currently, only +websockets devices are supported, but it can be extended for any protocol like +Webservices. + +Other modules can extend this one in order to use the widget. diff --git a/web_widget_remote_measure/readme/ROADMAP.rst b/web_widget_remote_measure/readme/ROADMAP.rst new file mode 100644 index 000000000..e21f0ea6e --- /dev/null +++ b/web_widget_remote_measure/readme/ROADMAP.rst @@ -0,0 +1,10 @@ +Current support: + +- Websockets connection +- F501 protocol on continuous message stream. + +But this is a commonground to add: + +- Other connection interfaces like Webservices APIs +- Other device protocols. +- Active device controls, la Tare, resets, etc. diff --git a/web_widget_remote_measure/readme/USAGE.rst b/web_widget_remote_measure/readme/USAGE.rst new file mode 100644 index 000000000..acbfc0635 --- /dev/null +++ b/web_widget_remote_measure/readme/USAGE.rst @@ -0,0 +1,16 @@ +The remote device has to be in the users network so their web clients can reach them. + +In order to test a device you can: + +#. Go to *Settings > Technical > Devices > Remote devices* +#. In the Kanban view you'll wich devices can be reached as they'll have a green dot in + their card. +#. Go to one of those and click *Edit*. +#. You can start measuring from the remote device in the *Test measure* field. + +On the technical side, you can use the widget in your own `Float``. You'll need to +provide an uom field so records that aren't in that UoM don't measure from the device. + +.. code:: xml + + diff --git a/web_widget_remote_measure/security/ir.model.access.csv b/web_widget_remote_measure/security/ir.model.access.csv new file mode 100644 index 000000000..04f222591 --- /dev/null +++ b/web_widget_remote_measure/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +web_widget_remote_measure.access_remote_measure_device_user,access_remote_measure_device users,web_widget_remote_measure.model_remote_measure_device,base.group_user,1,0,0,0 +web_widget_remote_measure.access_remote_measure_device_admin,access_remote_measure_device admin,web_widget_remote_measure.model_remote_measure_device,base.group_system,1,1,1,1 diff --git a/web_widget_remote_measure/static/description/icon.png b/web_widget_remote_measure/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..613498008d8e8001c281b288d122ce4cdafea478 GIT binary patch literal 9652 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 + + + + + +Remote Measure Devices Input + + + +
+

Remote Measure Devices Input

+ + +

Beta License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

+

This module allows to input data from remote devices in your network. Currently, only +websockets devices are supported, but it can be extended for any protocol like +Webservices.

+

Other modules can extend this one in order to use the widget.

+

Table of contents

+ +
+

Configuration

+

To configure your remote devices:

+
    +
  1. Go to Settings > Technical > Devices > Remote devices
  2. +
  3. Create a new one configuring the required info.
  4. +
  5. If the devices has an special port, set it up in the host data: e.g.: 10.1.1.2:3210
  6. +
+
+
+

Usage

+

The remote device has to be in the users network so their web clients can reach them.

+

In order to test a device you can:

+
    +
  1. Go to Settings > Technical > Devices > Remote devices
  2. +
  3. In the Kanban view you’ll wich devices can be reached as they’ll have a green dot in +their card.
  4. +
  5. Go to one of those and click Edit.
  6. +
  7. You can start measuring from the remote device in the Test measure field.
  8. +
+

On the technical side, you can use the widget in your own Float`. You’ll need to +provide an uom field so records that aren’t in that UoM don’t measure from the device.

+
+<field name="float_field" widget="remote_measure" options="{'remote_device_field': 'measure_device_id', 'uom_field': 'uom_id'}" />
+
+
+
+

Known issues / Roadmap

+

Current support:

+
    +
  • Websockets connection
  • +
  • F501 protocol on continuous message stream.
  • +
+

But this is a commonground to add:

+
    +
  • Other connection interfaces like Webservices APIs
  • +
  • Other device protocols.
  • +
  • Active device controls, la Tare, resets, etc.
  • +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

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.

+

Current maintainer:

+

chienandalu

+

This module is part of the OCA/web project on GitHub.

+

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

+
+
+
+ + diff --git a/web_widget_remote_measure/static/src/js/remote_measure_device_kanban_widget.esm.js b/web_widget_remote_measure/static/src/js/remote_measure_device_kanban_widget.esm.js new file mode 100644 index 000000000..312197856 --- /dev/null +++ b/web_widget_remote_measure/static/src/js/remote_measure_device_kanban_widget.esm.js @@ -0,0 +1,33 @@ +/** @odoo-module **/ +import Widget from "web.Widget"; +import widgetRegistry from "web.widget_registry"; + +const RemoteMeasureDeviceStatusWidget = Widget.extend({ + template: "web_widget_remote_measure.measure_device_status", + xmlDependencies: [ + "/web_widget_remote_measure/static/src/xml/measure_device_status.xml", + ], + init(_parent, _data, options) { + this._super(...arguments); + this.className = "text-muted"; + this.title = "Requesting status..."; + this.host = options.attrs.host; + }, + async willStart() { + await this._super(...arguments); + const socket = new WebSocket(this.host); + socket.onerror = async () => { + this.className = "text-danger"; + this.title = "Device is down"; + this.renderElement(); + }; + socket.onmessage = async () => { + socket.close(); + this.className = "text-success"; + this.title = "Device ready"; + this.renderElement(); + }; + }, +}); + +widgetRegistry.add("remote_measure_device_status", RemoteMeasureDeviceStatusWidget); diff --git a/web_widget_remote_measure/static/src/js/remote_measure_widget.esm.js b/web_widget_remote_measure/static/src/js/remote_measure_widget.esm.js new file mode 100644 index 000000000..d17170f4d --- /dev/null +++ b/web_widget_remote_measure/static/src/js/remote_measure_widget.esm.js @@ -0,0 +1,359 @@ +/** @odoo-module **/ +// TODO: Implement in OWL in v16. It should have be a much much simpler implementation. +import {FieldFloat} from "web.basic_fields"; +import {_lt} from "@web/core/l10n/translation"; +import {_t} from "web.translation"; +import fieldRegistry from "web.field_registry"; +import session from "web.session"; + +// Animate the measure steps for each measure received. +export const nextState = { + "fa-thermometer-empty": "fa-thermometer-quarter", + "fa-thermometer-quarter": "fa-thermometer-half", + "fa-thermometer-half": "fa-thermometer-three-quarters", + "fa-thermometer-three-quarters": "fa-thermometer-full", + "fa-thermometer-full": "fa-thermometer-empty", +}; + +export const RemoteMeasureMixin = { + /** + * F501 Protocol response: + * [STX][status1][status2][data][ETX] + * - status1 beign weight status: \x20 (space) for stable weight and ? for unstable + * - status2 beign weight sign: + for positive and - for negative. + * - data being the weight itself with 6 characters for weight and one . for the + * decimal dot + * + * @param {String} msg ASCII string + * @returns {Object} with the value and the stable flag + */ + _proccess_msg_f501(msg) { + return { + stable: msg[1] === "\x20", + value: parseFloat(msg.slice(2, 10)), + }; + }, + /** + * Implemented for a continous remote stream + * TODO: Abstract more the possible device scenarios + */ + _connect_to_websockets() { + try { + this.socket = new WebSocket(this.host); + } catch (error) { + // Avoid websockets security error. Local devices won't have wss normally + if (error.code === 18) { + return; + } + throw error; + } + var icon = "fa-thermometer-empty"; + var stream_success_counter = 10; + this.socket.onmessage = async (msg) => { + const data = await msg.data.text(); + const processed_data = this[`_proccess_msg_${this.protocol}`](data); + if (!processed_data.stable) { + stream_success_counter = 5; + } + if (processed_data.stable && !stream_success_counter) { + this._stableMeasure(); + this._closeSocket(); + this._awaitingMeasure(); + this._recordMeasure(); + return; + } + this._unstableMeasure(); + + if (stream_success_counter) { + --stream_success_counter; + } + icon = this._nextStateIcon(icon); + this.amount = processed_data.value; + this._setMeasure(); + }; + this.socket.onerror = () => { + this._awaitingMeasure(); + }; + }, + /** + * Implement for your device protocol service + */ + _connect_to_webservices() { + return; + }, + /** + * Convert the measured units to the units expecte by the record if different + * @param {Number} amount + * @returns {Number} converted amount + */ + _compute_quantity(amount) { + if (this.uom.id === this.device_uom.id) { + return amount; + } + let converted_amount = amount / this.remote_device_data.uom_factor; + converted_amount *= this.uom.factor; + return converted_amount; + }, + /** + * Set value + */ + async _setMeasure() { + if (isNaN(this.amount)) { + return; + } + this.amount = this._compute_quantity(this.amount); + if (this.start_add) { + this.amount += this.input_val; + } + this.$input.val(this.amount.toLocaleString(this.locale_code)); + this._setValue(this.$input.val()); + }, + /** + * Procure to close the socket whenever the widget stops being used + */ + _closeSocket() { + if (this.socket) { + this.socket.close(); + } + }, + /** + * Animate the measure steps for each measure received. + * @param {String} icon + * @returns {String} next icon + */ + _nextStateIcon(icon) { + const next_icon = nextState[icon]; + this.$icon.removeClass(icon); + this.$icon.addClass(next_icon); + return next_icon; + }, + /** + * While a measure is not stable the button will be red + */ + _unstableMeasure() { + this.$stop_measure.removeClass("btn-primary btn-success"); + this.$stop_measure.addClass("btn-danger"); + }, + /** + * Once we consider the measure is stable render the button as green + */ + _stableMeasure() { + this.$stop_measure.removeClass("btn-primary btn-danger"); + this.$stop_measure.addClass("btn-success"); + }, + /** + * While the widget isn't querying it will be purple as a signal that we can start + */ + _awaitingMeasure() { + this.$start_measure.removeClass("btn-success btn-danger"); + this.$start_measure.addClass("btn-primary"); + this.$stop_measure.addClass("d-none"); + this.$start_measure.removeClass("d-none"); + if (this.$start_measure_add) { + this.$start_measure_add.removeClass("d-none"); + } + }, + /** + * + */ + _recordMeasure() { + this.start_add = false; + this.input_val = this.amount; + this.start_add = false; + }, + /** + * Start requesting measures from the remote device + * @param {MouseEvent} ev + */ + _onMeasure(ev) { + ev.preventDefault(); + this.$start_measure.addClass("d-none"); + this.$stop_measure.removeClass("d-none"); + this.$icon = this.$stop_measure.find("i"); + this[`_connect_to_${this.connection_mode}`](); + }, + _onMeasureAdd(ev) { + ev.preventDefault(); + this.start_add = true; + this.$start_measure.addClass("d-none"); + this.$start_measure_add.addClass("d-none"); + this.$stop_measure.removeClass("d-none"); + this.$icon = this.$stop_measure.find("i"); + this[`_connect_to_${this.connection_mode}`](); + }, + /** + * Validate the requested measure + * @param {MouseEvent} ev + */ + _onValidateMeasure(ev) { + ev.preventDefault(); + this._closeSocket(); + this._awaitingMeasure(); + this._recordMeasure(); + }, + /** + * Remote measure handle to start measuring + * @returns {jQueryElement} + */ + _addRemoteMeasureWidgetStart() { + return $( + ` + + + + ` + ).on("click", this._onMeasure.bind(this)); + }, + /** + * Remote measure handle to start measuring + * @returns {jQueryElement} + */ + _addRemoteMeasureWidgetStartAdd() { + return $( + ` + + + + ` + ).on("click", this._onMeasureAdd.bind(this)); + }, + /** + * Remote measure handle to stop and register measuring + * @returns {jQueryElement} + */ + _addRemoteMeasureWidgetStop() { + return $( + ` + + + + ` + ).on("click", this._onValidateMeasure.bind(this)); + }, +}; + +export const RemoteMeasure = FieldFloat.extend(RemoteMeasureMixin, { + description: _lt("Remote Measure"), + className: "o_field_remote_device o_field_number", + tagName: "span", + isQuickEditable: true, + resetOnAnyFieldChange: true, + events: Object.assign({}, FieldFloat.prototype.events, { + focusin: "_onFocusIn", + }), + /** + * Setup the field layout and the remote device parameters + */ + init() { + this._super(...arguments); + if (this.mode === "edit") { + this.tagName = "div"; + this.className += " o_input"; + } + this.locale_code = _t.database.parameters.code.replace("_", "-"); + this.decimal_separator = _t.database.parameters.decimal_point; + this.thousands_sep = _t.database.parameters.thousands_sep; + this.remote_device_field = this.nodeOptions.remote_device_field; + this.default_user_device = this.nodeOptions.default_user_device; + if (this.nodeOptions.remote_device_field === "id") { + this.remote_device_data = this.recordData; + } else if (this.remote_device_field) { + this.remote_device_data = this.recordData[this.remote_device_field].data; + } + this.uom = this.recordData[this.nodeOptions.uom_field].data; + this.allow_additive_measure = this.nodeOptions.allow_additive_measure; + // Add to your view options so you can log requests and responses + }, + /** + * Request the configured remote device info + */ + async willStart() { + await this._super(...arguments); + // Try to get the user's preferred device if any + if (!this.remote_device_data && this.default_user_device) { + [this.remote_device_data] = await this._rpc({ + model: "res.users", + method: "read", + args: [session.uid, ["remote_measure_device_id"]], + }); + if (!this.remote_device_data.remote_measure_device_id) { + return; + } + if (this.remote_device_data) { + this.remote_device_data.id = + this.remote_device_data.remote_measure_device_id[0]; + } + } + if (!this.remote_device_data || !this.uom) { + return; + } + [this.remote_device_data] = await this._rpc({ + model: "remote.measure.device", + method: "read", + args: [this.remote_device_data.id, []], + }); + [this.uom] = await this._rpc({ + model: "uom.uom", + method: "read", + args: [this.uom.id, []], + }); + this.uom_category = this.uom.category_id[0]; + this.device_uom_category = this.remote_device_data.uom_category_id[0]; + this.device_uom = this.remote_device_data.uom_id[0]; + this.host = this.remote_device_data && this.remote_device_data.host; + this.protocol = this.remote_device_data && this.remote_device_data.protocol; + this.connection_mode = + this.remote_device_data && this.remote_device_data.connection_mode; + }, + /** + * Set de widget layout up + * @returns {Promise} + */ + _renderEdit() { + this.$el.empty(); + var def = this._prepareInput(this.$input).appendTo(this.$el); + // From locale format + if (this.input_val === undefined) { + let pre_value = this.$input.val() || "0"; + pre_value = pre_value.replace(this.thousands_sep, ""); + pre_value = pre_value.replace(this.decimal_separator, "."); + this.input_val = parseFloat(pre_value); + } + this.start_add = false; + const [device_uom = undefined] = + (this.remote_device_data && this.remote_device_data.uom_id) || []; + if ( + !this.remote_device_data || + !this.uom || + !device_uom || + this.uom_category !== this.device_uom_category + ) { + return def; + } + this.$start_measure = this._addRemoteMeasureWidgetStart(); + this.$stop_measure = this._addRemoteMeasureWidgetStop(); + if (this.allow_additive_measure && this.input_val > 0) { + this.$start_measure_add = this._addRemoteMeasureWidgetStartAdd(); + this.$el.prepend(this.$start_measure_add); + } + this.$el.prepend(this.$start_measure, this.$stop_measure); + return def; + }, + /** + * Ensure that the socket is allways closed + */ + destroy() { + this._closeSocket(); + this._super.apply(this, arguments); + }, + /** + * Auto select all the content + */ + _onFocusIn: function () { + // Auto select all content when user enters into fields with this + // widget. + this.$input.select(); + }, +}); + +fieldRegistry.add("remote_measure", RemoteMeasure); diff --git a/web_widget_remote_measure/static/src/scss/remote_measure_widget.scss b/web_widget_remote_measure/static/src/scss/remote_measure_widget.scss new file mode 100644 index 000000000..16c15b980 --- /dev/null +++ b/web_widget_remote_measure/static/src/scss/remote_measure_widget.scss @@ -0,0 +1,30 @@ +.o_field_widget { + &.o_field_remote_device { + display: inline-flex; + > span, + > button { + flex: 0 0 auto; + } + } + &.o_field_remote_device { + &.o_input { + align-items: baseline; + + > input { + width: 100px; + flex: 1 0 auto; + } + } + } +} +.o_list_view { + .o_list_table { + .o_data_row.o_selected_row + > .o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) { + .o_field_remote_device input { + width: 0; + margin: 0 4px; + } + } + } +} diff --git a/web_widget_remote_measure/static/src/xml/measure_device_status.xml b/web_widget_remote_measure/static/src/xml/measure_device_status.xml new file mode 100644 index 000000000..fbb7f6bc5 --- /dev/null +++ b/web_widget_remote_measure/static/src/xml/measure_device_status.xml @@ -0,0 +1,15 @@ + + diff --git a/web_widget_remote_measure/views/remote_measure_device_views.xml b/web_widget_remote_measure/views/remote_measure_device_views.xml new file mode 100644 index 000000000..0d6bca325 --- /dev/null +++ b/web_widget_remote_measure/views/remote_measure_device_views.xml @@ -0,0 +1,92 @@ + + + + remote.measure.device + +
+ + + +
+

+
+ + + + + + + + + + +
+
+
+
+ + remote.measure.device + + + + + + + + + + + + remote.measure.device + + + + + + + + +
+
+
+
+ + + + +
+
+
    +
  • +
  • +
+
+
+
+
+
+
+
+ + Remote Devices + remote.measure.device + kanban,tree,form + + + +
diff --git a/web_widget_remote_measure/views/res_users_views.xml b/web_widget_remote_measure/views/res_users_views.xml new file mode 100644 index 000000000..570b5370a --- /dev/null +++ b/web_widget_remote_measure/views/res_users_views.xml @@ -0,0 +1,17 @@ + + + + res.users + + + + + + + + + +