fix(fusion_iot): respect company temperature-unit preference in sensor UI
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) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — IoT Integration',
|
'name': 'Fusion Plating — IoT Integration',
|
||||||
'version': '19.0.0.1.0',
|
'version': '19.0.0.2.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'Wire physical tank sensors to Fusion Plating — live '
|
'summary': 'Wire physical tank sensors to Fusion Plating — live '
|
||||||
'temperature / chemistry readings with auto quality holds '
|
'temperature / chemistry readings with auto quality holds '
|
||||||
|
|||||||
@@ -54,14 +54,47 @@ class FpTankReading(models.Model):
|
|||||||
default=fields.Datetime.now, index=True,
|
default=fields.Datetime.now, index=True,
|
||||||
)
|
)
|
||||||
value = fields.Float(
|
value = fields.Float(
|
||||||
string='Value', required=True, digits=(12, 4),
|
string='Value (raw)', required=True, digits=(12, 4),
|
||||||
help='Numeric reading in the parameter\'s native unit (°C, pH, '
|
help='Stored reading in the sensor\'s canonical unit (for '
|
||||||
'µS/cm, etc.).',
|
'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(
|
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(
|
source = fields.Selection(
|
||||||
[
|
[
|
||||||
('iot_proxy', 'IoT Proxy (Pi)'),
|
('iot_proxy', 'IoT Proxy (Pi)'),
|
||||||
@@ -86,13 +119,12 @@ class FpTankReading(models.Model):
|
|||||||
string='Display', compute='_compute_display_name', store=True,
|
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):
|
def _compute_display_name(self):
|
||||||
for r in self:
|
for r in self:
|
||||||
sensor = r.sensor_id.name or 'sensor'
|
sensor = r.sensor_id.name or 'sensor'
|
||||||
at = fields.Datetime.to_string(r.reading_at) if r.reading_at else ''
|
at = fields.Datetime.to_string(r.reading_at) if r.reading_at else ''
|
||||||
unit = r.unit or ''
|
r.display_name = f'{sensor} — {r.display_value:.2f} {r.display_unit} @ {at}'
|
||||||
r.display_name = f'{sensor} — {r.value:.2f} {unit} @ {at}'
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Create hook — evaluate against spec + raise a quality hold if we
|
# Create hook — evaluate against spec + raise a quality hold if we
|
||||||
|
|||||||
@@ -98,13 +98,38 @@ class FpTankSensor(models.Model):
|
|||||||
# Cached latest-reading fields (for quick display in list views)
|
# Cached latest-reading fields (for quick display in list views)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
last_reading_value = fields.Float(
|
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_at = fields.Datetime(string='Latest Reading', readonly=True)
|
||||||
last_reading_in_spec = fields.Boolean(
|
last_reading_in_spec = fields.Boolean(
|
||||||
string='In Spec?', readonly=True,
|
string='In Spec?', readonly=True,
|
||||||
help='Computed from the last reading vs alert_min/alert_max.',
|
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(
|
reading_ids = fields.One2many(
|
||||||
'fp.tank.reading', 'sensor_id', string='Reading History',
|
'fp.tank.reading', 'sensor_id', string='Reading History',
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
<field name="sensor_id"/>
|
<field name="sensor_id"/>
|
||||||
<field name="tank_id" optional="show"/>
|
<field name="tank_id" optional="show"/>
|
||||||
<field name="parameter_id" optional="hide"/>
|
<field name="parameter_id" optional="hide"/>
|
||||||
<field name="value"/>
|
<field name="display_value"/>
|
||||||
<field name="unit"/>
|
<field name="display_unit"/>
|
||||||
|
<field name="value" optional="hide" string="Value (°C raw)"/>
|
||||||
<field name="in_spec" widget="boolean_toggle"/>
|
<field name="in_spec" widget="boolean_toggle"/>
|
||||||
<field name="source" optional="hide"/>
|
<field name="source" optional="hide"/>
|
||||||
<field name="hold_id" optional="show"/>
|
<field name="hold_id" optional="show"/>
|
||||||
@@ -39,8 +40,10 @@
|
|||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="reading_at"/>
|
<field name="reading_at"/>
|
||||||
<field name="value"/>
|
<field name="display_value" readonly="1"/>
|
||||||
<field name="unit" readonly="1"/>
|
<field name="display_unit" readonly="1"/>
|
||||||
|
<field name="value" readonly="1"
|
||||||
|
string="Stored Value (°C, canonical)"/>
|
||||||
<field name="in_spec" readonly="1"/>
|
<field name="in_spec" readonly="1"/>
|
||||||
<field name="hold_id" readonly="1"/>
|
<field name="hold_id" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
<field name="parameter_id"/>
|
<field name="parameter_id"/>
|
||||||
<field name="device_serial" optional="show"/>
|
<field name="device_serial" optional="show"/>
|
||||||
<field name="iot_device_id" optional="hide"/>
|
<field name="iot_device_id" optional="hide"/>
|
||||||
<field name="last_reading_value"/>
|
<field name="last_reading_display"/>
|
||||||
|
<field name="last_reading_display_unit"/>
|
||||||
|
<field name="last_reading_value" optional="hide" string="Latest Raw (°C)"/>
|
||||||
<field name="last_reading_at"/>
|
<field name="last_reading_at"/>
|
||||||
<field name="last_reading_in_spec" widget="boolean_toggle"/>
|
<field name="last_reading_in_spec" widget="boolean_toggle"/>
|
||||||
<field name="reading_count"/>
|
<field name="reading_count"/>
|
||||||
@@ -76,7 +78,11 @@
|
|||||||
help="Leave 0 to inherit from the bath parameter's target_max."/>
|
help="Leave 0 to inherit from the bath parameter's target_max."/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Most Recent Reading">
|
<group string="Most Recent Reading">
|
||||||
<field name="last_reading_value" readonly="1"/>
|
<field name="last_reading_display" readonly="1"/>
|
||||||
|
<field name="last_reading_display_unit" readonly="1"/>
|
||||||
|
<field name="last_reading_value" readonly="1"
|
||||||
|
string="Stored (°C canonical)"
|
||||||
|
groups="base.group_no_one"/>
|
||||||
<field name="last_reading_at" readonly="1"/>
|
<field name="last_reading_at" readonly="1"/>
|
||||||
<field name="last_reading_in_spec" readonly="1" widget="boolean_toggle"/>
|
<field name="last_reading_in_spec" readonly="1" widget="boolean_toggle"/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
<field name="device_kind"/>
|
<field name="device_kind"/>
|
||||||
<field name="device_serial"/>
|
<field name="device_serial"/>
|
||||||
<field name="parameter_id"/>
|
<field name="parameter_id"/>
|
||||||
<field name="last_reading_value"/>
|
<field name="last_reading_display"/>
|
||||||
|
<field name="last_reading_display_unit"/>
|
||||||
<field name="last_reading_at" readonly="1"/>
|
<field name="last_reading_at" readonly="1"/>
|
||||||
<field name="last_reading_in_spec" widget="boolean_toggle"/>
|
<field name="last_reading_in_spec" widget="boolean_toggle"/>
|
||||||
<field name="alert_on_out_of_spec" widget="boolean_toggle"/>
|
<field name="alert_on_out_of_spec" widget="boolean_toggle"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user