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 000000000..613498008 Binary files /dev/null and b/web_widget_remote_measure/static/description/icon.png differ diff --git a/web_widget_remote_measure/static/description/index.html b/web_widget_remote_measure/static/description/index.html new file mode 100644 index 000000000..751761aa3 --- /dev/null +++ b/web_widget_remote_measure/static/description/index.html @@ -0,0 +1,470 @@ + + + + + + +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 + + + + + + + + + +