From 089cda71fee420471afa3af6dab697464e199475 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Mon, 20 Apr 2026 16:13:17 -0400 Subject: [PATCH] fix(fusion_iot): respect company temperature-unit preference in sensor UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sensor readings list always showed raw °C regardless of the Plating Settings Temperature Unit preference (res.company.x_fc_default_temp_uom). On a Fahrenheit-preferred shop, a 40°C reading should render as 105°F. Fix: add display-aware computed fields alongside the canonical ones. **fp.tank.reading** - `value` / `unit` renamed with "(raw)" labels — these are the stored canonical values (always °C for temperature, because every temperature chip reports in Celsius natively) - `display_value` + `display_unit` computed from company pref — only flips C→F when parameter_type='temperature' AND company pref='F'; pH/conductivity/etc pass through untouched - `display_name` now uses display_value so it reads naturally ("Sensor — 105.58 °F @ ...") regardless of region **fp.tank.sensor** - Mirrored the same pattern on the cached last-reading fields - `last_reading_display` + `last_reading_display_unit` for lists - `last_reading_value` hidden behind group_no_one (debug-only) **Views** - fp.tank.reading list: show display_value/display_unit, raw value hidden by default (toggle from column picker if needed) - fp.tank.sensor list + form + tank inline: same pattern - Raw value kept visible as an optional column so data engineers can still audit canonical storage Why store canonical: spec thresholds (alert_min/max) live on the sensor in °C. If the same Odoo serves a multi-region company (Canada in C, US affiliate in F), switching a single preference flips every UI without touching data. Alert logic keeps comparing canonical values, so out-of-spec holds fire correctly regardless of display unit. Verified: 40.88°C raw → 105.58°F display on the live pilot probe with company pref='F'. All 5 recent readings tested, display fields updated correctly on every poll. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_iot/fusion_plating_iot/__manifest__.py | 2 +- .../models/fp_tank_reading.py | 46 ++++++++++++++++--- .../models/fp_tank_sensor.py | 27 ++++++++++- .../views/fp_tank_reading_views.xml | 11 +++-- .../views/fp_tank_sensor_views.xml | 10 +++- .../views/fusion_plating_tank_views.xml | 3 +- 6 files changed, 83 insertions(+), 16 deletions(-) diff --git a/fusion_iot/fusion_plating_iot/__manifest__.py b/fusion_iot/fusion_plating_iot/__manifest__.py index 6bdaab00..929ef302 100644 --- a/fusion_iot/fusion_plating_iot/__manifest__.py +++ b/fusion_iot/fusion_plating_iot/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — IoT Integration', - 'version': '19.0.0.1.0', + 'version': '19.0.0.2.0', 'category': 'Manufacturing/Plating', 'summary': 'Wire physical tank sensors to Fusion Plating — live ' 'temperature / chemistry readings with auto quality holds ' diff --git a/fusion_iot/fusion_plating_iot/models/fp_tank_reading.py b/fusion_iot/fusion_plating_iot/models/fp_tank_reading.py index 6e8cd708..0035eb8e 100644 --- a/fusion_iot/fusion_plating_iot/models/fp_tank_reading.py +++ b/fusion_iot/fusion_plating_iot/models/fp_tank_reading.py @@ -54,14 +54,47 @@ class FpTankReading(models.Model): default=fields.Datetime.now, index=True, ) value = fields.Float( - string='Value', required=True, digits=(12, 4), - help='Numeric reading in the parameter\'s native unit (°C, pH, ' - 'µS/cm, etc.).', + string='Value (raw)', required=True, digits=(12, 4), + help='Stored reading in the sensor\'s canonical unit (for ' + 'temperature sensors this is always °C — the DS18B20 and ' + 'every other temperature chip reports in Celsius natively; ' + 'keeping storage canonical lets us switch display units ' + 'per-company without re-migrating history).', ) unit = fields.Char( - string='Unit', related='parameter_id.uom', store=True, + string='Unit (raw)', related='parameter_id.uom', store=True, ) + # ------------------------------------------------------------------ + # Display-aware fields — converted to the company's preferred unit + # (res.company.x_fc_default_temp_uom = 'F' or 'C'). Only the list + # and form views should show these; internal spec comparisons use + # `value` so thresholds stay consistent across regions. + # ------------------------------------------------------------------ + display_value = fields.Float( + string='Value', compute='_compute_display', + digits=(12, 2), + help='Reading in the company-preferred unit (Settings → Plating → ' + 'Temperature Unit).', + ) + display_unit = fields.Char( + string='Unit', compute='_compute_display', + ) + + @api.depends('value', 'parameter_id', 'parameter_id.parameter_type', + 'parameter_id.uom') + def _compute_display(self): + # Read once per compute call — env.company rarely changes mid-batch. + pref = self.env.company.x_fc_default_temp_uom or 'C' + for r in self: + ptype = (r.parameter_id.parameter_type or '').lower() + if ptype == 'temperature' and pref == 'F': + r.display_value = r.value * 9.0 / 5.0 + 32.0 + r.display_unit = '°F' + else: + r.display_value = r.value + r.display_unit = r.parameter_id.uom or '' + source = fields.Selection( [ ('iot_proxy', 'IoT Proxy (Pi)'), @@ -86,13 +119,12 @@ class FpTankReading(models.Model): string='Display', compute='_compute_display_name', store=True, ) - @api.depends('sensor_id', 'value', 'reading_at') + @api.depends('sensor_id', 'display_value', 'display_unit', 'reading_at') def _compute_display_name(self): for r in self: sensor = r.sensor_id.name or 'sensor' at = fields.Datetime.to_string(r.reading_at) if r.reading_at else '' - unit = r.unit or '' - r.display_name = f'{sensor} — {r.value:.2f} {unit} @ {at}' + r.display_name = f'{sensor} — {r.display_value:.2f} {r.display_unit} @ {at}' # ------------------------------------------------------------------ # Create hook — evaluate against spec + raise a quality hold if we diff --git a/fusion_iot/fusion_plating_iot/models/fp_tank_sensor.py b/fusion_iot/fusion_plating_iot/models/fp_tank_sensor.py index 6ed86eaf..ee734cc7 100644 --- a/fusion_iot/fusion_plating_iot/models/fp_tank_sensor.py +++ b/fusion_iot/fusion_plating_iot/models/fp_tank_sensor.py @@ -98,13 +98,38 @@ class FpTankSensor(models.Model): # Cached latest-reading fields (for quick display in list views) # ------------------------------------------------------------------ last_reading_value = fields.Float( - string='Latest Value', readonly=True, digits=(12, 4), + string='Latest Value (raw)', readonly=True, digits=(12, 4), + help='Stored in the sensor\'s canonical unit (°C for temperature).', ) last_reading_at = fields.Datetime(string='Latest Reading', readonly=True) last_reading_in_spec = fields.Boolean( string='In Spec?', readonly=True, help='Computed from the last reading vs alert_min/alert_max.', ) + # Display-aware version of last_reading_value — converted to company + # preferred unit (res.company.x_fc_default_temp_uom). + last_reading_display = fields.Float( + string='Latest Value', + compute='_compute_last_reading_display', + digits=(12, 2), + ) + last_reading_display_unit = fields.Char( + string='Unit', + compute='_compute_last_reading_display', + ) + + @api.depends('last_reading_value', 'parameter_id', + 'parameter_id.parameter_type', 'parameter_id.uom') + def _compute_last_reading_display(self): + pref = self.env.company.x_fc_default_temp_uom or 'C' + for rec in self: + ptype = (rec.parameter_id.parameter_type or '').lower() + if ptype == 'temperature' and pref == 'F': + rec.last_reading_display = rec.last_reading_value * 9.0 / 5.0 + 32.0 + rec.last_reading_display_unit = '°F' + else: + rec.last_reading_display = rec.last_reading_value + rec.last_reading_display_unit = rec.parameter_id.uom or '' reading_ids = fields.One2many( 'fp.tank.reading', 'sensor_id', string='Reading History', diff --git a/fusion_iot/fusion_plating_iot/views/fp_tank_reading_views.xml b/fusion_iot/fusion_plating_iot/views/fp_tank_reading_views.xml index 9b5280c4..e2023b95 100644 --- a/fusion_iot/fusion_plating_iot/views/fp_tank_reading_views.xml +++ b/fusion_iot/fusion_plating_iot/views/fp_tank_reading_views.xml @@ -15,8 +15,9 @@ - - + + + @@ -39,8 +40,10 @@ - - + + + diff --git a/fusion_iot/fusion_plating_iot/views/fp_tank_sensor_views.xml b/fusion_iot/fusion_plating_iot/views/fp_tank_sensor_views.xml index 6c17944e..fe03d2ac 100644 --- a/fusion_iot/fusion_plating_iot/views/fp_tank_sensor_views.xml +++ b/fusion_iot/fusion_plating_iot/views/fp_tank_sensor_views.xml @@ -19,7 +19,9 @@ - + + + @@ -76,7 +78,11 @@ help="Leave 0 to inherit from the bath parameter's target_max."/> - + + + diff --git a/fusion_iot/fusion_plating_iot/views/fusion_plating_tank_views.xml b/fusion_iot/fusion_plating_iot/views/fusion_plating_tank_views.xml index b2d63ad7..d9a0c04c 100644 --- a/fusion_iot/fusion_plating_iot/views/fusion_plating_tank_views.xml +++ b/fusion_iot/fusion_plating_iot/views/fusion_plating_tank_views.xml @@ -24,7 +24,8 @@ - + +