chore(plating): de-dash fusion_plating_iot too
Same em-dash -> hyphen sweep applied to fusion_plating_iot (lives under fusion_iot/ so the main commit missed it). Comments/strings only; no functional dashes in this module. Keeps git in sync with the in-place de-dash already applied to entech. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,22 +4,22 @@
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — IoT Integration',
|
||||
'name': 'Fusion Plating - IoT Integration',
|
||||
'version': '19.0.2.0.0',
|
||||
'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 '
|
||||
'on out-of-spec.',
|
||||
'description': """
|
||||
Fusion Plating — IoT Integration
|
||||
Fusion Plating - IoT Integration
|
||||
================================
|
||||
|
||||
Bridges the generic `iot` module (IoT Box + device management) to
|
||||
plating-specific models:
|
||||
|
||||
* ``fp.tank.sensor`` — maps an ``iot.device`` to a
|
||||
* ``fp.tank.sensor`` - maps an ``iot.device`` to a
|
||||
``fusion.plating.tank`` (or a ``fusion.plating.bath``).
|
||||
* ``fp.tank.reading`` — time-series log of every sensor reading.
|
||||
* ``fp.tank.reading`` - time-series log of every sensor reading.
|
||||
* Auto-creates a ``fusion.plating.quality.hold`` when a reading
|
||||
falls outside the tank/bath's target range (per
|
||||
``fusion.plating.bath.parameter`` spec).
|
||||
|
||||
@@ -40,7 +40,7 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _parse_read_at(raw):
|
||||
"""Best-effort ISO-8601 parse — fall back to 'now' on garbage input."""
|
||||
"""Best-effort ISO-8601 parse - fall back to 'now' on garbage input."""
|
||||
from odoo.fields import Datetime as OdooDatetime
|
||||
if not raw:
|
||||
return OdooDatetime.now()
|
||||
@@ -62,20 +62,20 @@ class FpIotIngestController(http.Controller):
|
||||
methods=['POST'], csrf=False, save_session=False)
|
||||
def ingest(self, **_kwargs):
|
||||
"""Accept one-or-many sensor readings and land them in fp.tank.reading."""
|
||||
# Pull the shared secret from config — configured at install via
|
||||
# Pull the shared secret from config - configured at install via
|
||||
# data/ir_config_parameter_data.xml, but admins can rotate it
|
||||
# in Settings → Technical → System Parameters.
|
||||
expected = request.env['ir.config_parameter'].sudo().get_param(
|
||||
'fusion_plating_iot.ingest_token', ''
|
||||
)
|
||||
if not expected:
|
||||
_logger.warning('fp.iot.ingest: token not configured — all requests rejected')
|
||||
_logger.warning('fp.iot.ingest: token not configured - all requests rejected')
|
||||
return Response(
|
||||
json.dumps({'ok': False, 'error': 'token_not_configured'}),
|
||||
status=503, content_type='application/json',
|
||||
)
|
||||
|
||||
# Accept token via either header or payload body — some simple
|
||||
# Accept token via either header or payload body - some simple
|
||||
# sensors can't easily set custom headers.
|
||||
header_token = request.httprequest.headers.get('X-FP-IOT-Token', '')
|
||||
raw = request.httprequest.get_data(as_text=True) or ''
|
||||
@@ -124,10 +124,10 @@ class FpIotIngestController(http.Controller):
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
# Sub 7 — per-sensor rate-limit. Drop readings that arrive
|
||||
# Sub 7 - per-sensor rate-limit. Drop readings that arrive
|
||||
# inside the sensor's effective polling interval so the Pi
|
||||
# agent can happily poll every 30 s while the log only
|
||||
# retains a row every 15–30 min. Inactive sensors also
|
||||
# retains a row every 15-30 min. Inactive sensors also
|
||||
# dropped so disabled tanks don't clutter the log.
|
||||
if not sensor.active:
|
||||
skipped_interval += 1
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1
|
||||
|
||||
Default sensor types — the common ones every plating shop needs.
|
||||
Default sensor types - the common ones every plating shop needs.
|
||||
noupdate="1" so admins can tweak without losing edits on module upgrade.
|
||||
-->
|
||||
<odoo noupdate="1">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
"""Post-install hook — backfill new fields on existing live sensors.
|
||||
"""Post-install hook - backfill new fields on existing live sensors.
|
||||
|
||||
Runs once on every install/upgrade. Idempotent: checks before writing
|
||||
so re-runs don't overwrite user-edited values.
|
||||
|
||||
What it does:
|
||||
1. Populates `uuid` on any fp.tank.sensor record that doesn't have one
|
||||
(for sensors created BEFORE the uuid field existed — the create
|
||||
(for sensors created BEFORE the uuid field existed - the create
|
||||
override only covers new records).
|
||||
2. Sets a default `sensor_type_id` on sensors that don't have one yet,
|
||||
inferring from `device_kind` (DS18B20 / PT100 / PT1000 → Temperature,
|
||||
@@ -78,7 +78,7 @@ def _backfill_sensor_types(env):
|
||||
|
||||
|
||||
def _seed_entech_tanks_and_sensors(env):
|
||||
"""Sub 7 — seed 25 tanks (5 small + 20 big; 10 big inactive) with
|
||||
"""Sub 7 - seed 25 tanks (5 small + 20 big; 10 big inactive) with
|
||||
one temperature and one pH sensor each.
|
||||
|
||||
Idempotent: tanks keyed by `code` so re-runs skip existing rows.
|
||||
@@ -103,7 +103,7 @@ def _seed_entech_tanks_and_sensors(env):
|
||||
|
||||
facility = Facility.search([], limit=1)
|
||||
if not facility:
|
||||
_logger.warning('Sub 7 seed: no fusion.plating.facility found — '
|
||||
_logger.warning('Sub 7 seed: no fusion.plating.facility found - '
|
||||
'skipping tank seed. Create a facility first.')
|
||||
return
|
||||
|
||||
@@ -117,7 +117,7 @@ def _seed_entech_tanks_and_sensors(env):
|
||||
)
|
||||
if not temp_param or not ph_param:
|
||||
_logger.warning('Sub 7 seed: temperature / pH bath parameters '
|
||||
'not found — skipping. Seed bath parameters first.')
|
||||
'not found - skipping. Seed bath parameters first.')
|
||||
return
|
||||
|
||||
plan = []
|
||||
@@ -147,7 +147,7 @@ def _seed_entech_tanks_and_sensors(env):
|
||||
('parameter_id.parameter_type', '=', 'temperature'),
|
||||
]):
|
||||
Sensor.create({
|
||||
'name': '%s — Temperature' % tank.name,
|
||||
'name': '%s - Temperature' % tank.name,
|
||||
'tank_id': tank.id,
|
||||
'parameter_id': temp_param.id,
|
||||
'device_kind': 'ds18b20',
|
||||
@@ -160,7 +160,7 @@ def _seed_entech_tanks_and_sensors(env):
|
||||
('parameter_id.parameter_type', '=', 'ph'),
|
||||
]):
|
||||
Sensor.create({
|
||||
'name': '%s — pH' % tank.name,
|
||||
'name': '%s - pH' % tank.name,
|
||||
'tank_id': tank.id,
|
||||
'parameter_id': ph_param.id,
|
||||
'device_kind': 'ph',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
"""Sensor dashboard — a named group of related sensors.
|
||||
"""Sensor dashboard - a named group of related sensors.
|
||||
|
||||
Use case: a shop wants one "ENP Line — Tanks 1-4" view that aggregates
|
||||
Use case: a shop wants one "ENP Line - Tanks 1-4" view that aggregates
|
||||
temperature, pH, and level probes from four bath tanks into a single
|
||||
trending chart + alert count. A dashboard is just a logical grouping;
|
||||
actual rendering happens in the UI.
|
||||
@@ -14,7 +14,7 @@ from odoo import api, fields, models
|
||||
|
||||
class FpSensorDashboard(models.Model):
|
||||
_name = 'fp.sensor.dashboard'
|
||||
_description = 'Fusion Plating — Sensor Dashboard'
|
||||
_description = 'Fusion Plating - Sensor Dashboard'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'name'
|
||||
|
||||
@@ -52,7 +52,7 @@ class FpSensorDashboard(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': f'Sensors — {self.name}',
|
||||
'name': f'Sensors - {self.name}',
|
||||
'res_model': 'fp.tank.sensor',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('id', 'in', self.sensor_ids.ids)],
|
||||
@@ -63,7 +63,7 @@ class FpSensorDashboard(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': f'Readings — {self.name}',
|
||||
'name': f'Readings - {self.name}',
|
||||
'res_model': 'fp.tank.reading',
|
||||
'view_mode': 'graph,list,form',
|
||||
'domain': [('sensor_id', 'in', self.sensor_ids.ids)],
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
A richer taxonomy than the `device_kind` Selection on fp.tank.sensor.
|
||||
Types describe WHAT the sensor measures (temperature, pH, conductivity,
|
||||
level, etc.) plus the data type of its readings. Same type can back
|
||||
multiple hardware models — e.g. "Temperature" covers DS18B20, PT100,
|
||||
multiple hardware models - e.g. "Temperature" covers DS18B20, PT100,
|
||||
PT1000, thermocouple.
|
||||
|
||||
Seeded defaults ship with the module (see data/fp_sensor_type_data.xml)
|
||||
@@ -19,7 +19,7 @@ from odoo import fields, models
|
||||
|
||||
class FpSensorType(models.Model):
|
||||
_name = 'fp.sensor.type'
|
||||
_description = 'Fusion Plating — Sensor Type'
|
||||
_description = 'Fusion Plating - Sensor Type'
|
||||
_order = 'sequence, name'
|
||||
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
@@ -74,7 +74,7 @@ class FpSensorType(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': f'Sensors — {self.name}',
|
||||
'name': f'Sensors - {self.name}',
|
||||
'res_model': 'fp.tank.sensor',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('sensor_type_id', '=', self.id)],
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"""Time-series of sensor readings.
|
||||
|
||||
Every POST to /fp/iot/ingest (or every broadcast from the iot proxy)
|
||||
lands as a new row here. Kept intentionally append-only — we never
|
||||
lands as a new row here. Kept intentionally append-only - we never
|
||||
update or delete readings, which makes this the compliance log for
|
||||
bath-temperature history.
|
||||
|
||||
@@ -13,7 +13,7 @@ Auto-creates a fusion.plating.quality.hold when a reading falls
|
||||
outside the sensor's alert range AND the sensor has
|
||||
`alert_on_out_of_spec` enabled. The hold is created once per
|
||||
excursion (we don't spam a new hold for every reading during a
|
||||
sustained excursion) — tracked via the sensor's most-recent
|
||||
sustained excursion) - tracked via the sensor's most-recent
|
||||
`last_reading_in_spec` flag.
|
||||
"""
|
||||
|
||||
@@ -26,7 +26,7 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class FpTankReading(models.Model):
|
||||
_name = 'fp.tank.reading'
|
||||
_description = 'Fusion Plating — Tank Sensor Reading'
|
||||
_description = 'Fusion Plating - Tank Sensor Reading'
|
||||
_order = 'reading_at desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
|
||||
@@ -34,7 +34,7 @@ class FpTankReading(models.Model):
|
||||
'fp.tank.sensor', string='Sensor', required=True,
|
||||
ondelete='cascade', index=True,
|
||||
)
|
||||
# Denormalised for fast list views + kpi queries — auto-filled at
|
||||
# Denormalised for fast list views + kpi queries - auto-filled at
|
||||
# create time from sensor_id. Indexed for historical trending.
|
||||
tank_id = fields.Many2one(
|
||||
'fusion.plating.tank', string='Tank',
|
||||
@@ -56,7 +56,7 @@ class FpTankReading(models.Model):
|
||||
value = fields.Float(
|
||||
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 '
|
||||
'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).',
|
||||
@@ -66,7 +66,7 @@ class FpTankReading(models.Model):
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Display-aware fields — converted to the company's preferred unit
|
||||
# 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.
|
||||
@@ -84,7 +84,7 @@ class FpTankReading(models.Model):
|
||||
@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.
|
||||
# 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()
|
||||
@@ -96,7 +96,7 @@ class FpTankReading(models.Model):
|
||||
r.display_unit = r.parameter_id.uom_display or ''
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Deviation from setpoint — signed Δ from the sensor's effective target
|
||||
# Deviation from setpoint - signed Δ from the sensor's effective target
|
||||
# in the company-preferred unit. Zero if no setpoint defined.
|
||||
# ------------------------------------------------------------------
|
||||
deviation_from_target = fields.Float(
|
||||
@@ -184,10 +184,10 @@ class FpTankReading(models.Model):
|
||||
for r in self:
|
||||
sensor = r.sensor_id.name or 'sensor'
|
||||
at = fields.Datetime.to_string(r.reading_at) if r.reading_at else ''
|
||||
r.display_name = f'{sensor} — {r.display_value:.2f} {r.display_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
|
||||
# Create hook - evaluate against spec + raise a quality hold if we
|
||||
# just crossed INTO an out-of-spec state.
|
||||
# ------------------------------------------------------------------
|
||||
@api.model_create_multi
|
||||
@@ -197,7 +197,7 @@ class FpTankReading(models.Model):
|
||||
try:
|
||||
rec._evaluate_spec()
|
||||
except Exception:
|
||||
# Never let alert-logic break the ingest path — the
|
||||
# Never let alert-logic break the ingest path - the
|
||||
# reading itself is what matters for compliance. Log
|
||||
# and carry on.
|
||||
_logger.exception(
|
||||
@@ -256,7 +256,7 @@ class FpTankReading(models.Model):
|
||||
'hold_reason': 'out_of_spec',
|
||||
'description': description,
|
||||
'qty_on_hold': 1,
|
||||
# state defaults to 'on_hold' — leave it
|
||||
# state defaults to 'on_hold' - leave it
|
||||
}
|
||||
# Attach facility + work-centre context if the tank has them,
|
||||
# so the hold is actionable from the shop floor (operator can
|
||||
|
||||
@@ -9,7 +9,7 @@ device registered via the iot_drivers proxy) is mapped to exactly one
|
||||
tank or bath and measures ONE bath parameter (temperature, pH,
|
||||
conductivity, etc.).
|
||||
|
||||
The same tank can carry multiple sensors — e.g. a temp probe and a pH
|
||||
The same tank can carry multiple sensors - e.g. a temp probe and a pH
|
||||
probe. Each is its own fp.tank.sensor row.
|
||||
"""
|
||||
|
||||
@@ -18,17 +18,17 @@ from odoo import api, fields, models
|
||||
|
||||
class FpTankSensor(models.Model):
|
||||
_name = 'fp.tank.sensor'
|
||||
_description = 'Fusion Plating — Tank Sensor'
|
||||
_description = 'Fusion Plating - Tank Sensor'
|
||||
_order = 'tank_id, parameter_id'
|
||||
|
||||
name = fields.Char(
|
||||
string='Sensor Name', required=True,
|
||||
help='Human label (e.g. "Tank 3 — ENP temp").',
|
||||
help='Human label (e.g. "Tank 3 - ENP temp").',
|
||||
)
|
||||
uuid = fields.Char(
|
||||
string='UUID',
|
||||
copy=False, readonly=True, index=True,
|
||||
help='Stable logical identifier — survives hardware swaps. If a '
|
||||
help='Stable logical identifier - survives hardware swaps. If a '
|
||||
'probe dies and gets replaced, keep the UUID, change the '
|
||||
'device_serial. Every measurement tied to this UUID remains '
|
||||
'part of the same logical history.',
|
||||
@@ -36,7 +36,7 @@ class FpTankSensor(models.Model):
|
||||
sensor_type_id = fields.Many2one(
|
||||
'fp.sensor.type',
|
||||
string='Sensor Type',
|
||||
help='Taxonomy — temperature, pH, conductivity, etc. '
|
||||
help='Taxonomy - temperature, pH, conductivity, etc. '
|
||||
'Independent from hardware (a "temperature" sensor could be '
|
||||
'a DS18B20, PT100, or thermocouple; device_kind captures '
|
||||
'the hardware, sensor_type_id captures the role).',
|
||||
@@ -44,7 +44,7 @@ class FpTankSensor(models.Model):
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Physical device — either an Odoo iot.device (proxied through the Pi)
|
||||
# Physical device - either an Odoo iot.device (proxied through the Pi)
|
||||
# OR a direct-ingest sensor (skipping the proxy, posting straight to
|
||||
# /fp/iot/ingest with the shared secret + device_serial).
|
||||
# ------------------------------------------------------------------
|
||||
@@ -73,7 +73,7 @@ class FpTankSensor(models.Model):
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Where this sensor lives — can be ANY of tank / work_center /
|
||||
# Where this sensor lives - can be ANY of tank / work_center /
|
||||
# facility / named-location. Keep tank_id as the primary (most
|
||||
# sensors are bath-mounted) but allow the other three as
|
||||
# alternatives so we can mount probes on ovens, ambient air,
|
||||
@@ -84,22 +84,22 @@ class FpTankSensor(models.Model):
|
||||
)
|
||||
bath_id = fields.Many2one(
|
||||
'fusion.plating.bath', string='Bath',
|
||||
help='Optional — if the sensor is bound to a specific bath '
|
||||
help='Optional - if the sensor is bound to a specific bath '
|
||||
'chemistry rather than a physical tank.',
|
||||
)
|
||||
work_center_id = fields.Many2one(
|
||||
'fusion.plating.work.center', string='Work Centre',
|
||||
help='Alternative to tank_id — use when the sensor is attached '
|
||||
help='Alternative to tank_id - use when the sensor is attached '
|
||||
'to a station, oven, or line rather than a bath tank.',
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility', string='Facility',
|
||||
help='Alternative to tank_id / work_center_id — use for '
|
||||
help='Alternative to tank_id / work_center_id - use for '
|
||||
'facility-wide sensors (ambient, HVAC, perimeter).',
|
||||
)
|
||||
location_name = fields.Char(
|
||||
string='Location (free-text)',
|
||||
help='Free-text override when none of the above fit — e.g. '
|
||||
help='Free-text override when none of the above fit - e.g. '
|
||||
'"North bay wall", "Effluent pipe exit", "Rinse tank #2 '
|
||||
'roof". Shown alongside the structured location in views.',
|
||||
)
|
||||
@@ -113,7 +113,7 @@ class FpTankSensor(models.Model):
|
||||
effective_location = fields.Char(
|
||||
string='Location',
|
||||
compute='_compute_effective_location', store=True,
|
||||
help='Display string for the sensor location — prefers tank, '
|
||||
help='Display string for the sensor location - prefers tank, '
|
||||
'then work_center, then facility, then free-text.',
|
||||
)
|
||||
|
||||
@@ -133,7 +133,7 @@ class FpTankSensor(models.Model):
|
||||
rec.effective_location = ''
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Target / alerting behaviour — three concepts:
|
||||
# Target / alerting behaviour - three concepts:
|
||||
# setpoint → what we CONTROL TOWARD (dashboards, PID, trend baseline)
|
||||
# alert min → lower alarm boundary (quality hold fires below)
|
||||
# alert max → upper alarm boundary (quality hold fires above)
|
||||
@@ -164,7 +164,7 @@ class FpTankSensor(models.Model):
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Sub 7 — per-sensor polling interval
|
||||
# Sub 7 - per-sensor polling interval
|
||||
#
|
||||
# Blank on the sensor = inherit res.company default. The effective
|
||||
# value gates the ingest endpoint so too-frequent readings are
|
||||
@@ -175,7 +175,7 @@ class FpTankSensor(models.Model):
|
||||
help='How often a reading from this sensor should be stored. '
|
||||
'Leave blank to inherit the company-wide default. Readings '
|
||||
'that arrive inside this interval are dropped by the '
|
||||
'ingest endpoint — the database stays clean even if the '
|
||||
'ingest endpoint - the database stays clean even if the '
|
||||
'Pi agent polls more often.',
|
||||
)
|
||||
poll_interval_display = fields.Char(
|
||||
@@ -211,7 +211,7 @@ class FpTankSensor(models.Model):
|
||||
return self.env.company.x_fc_default_poll_interval_minutes or 30
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Effective target — resolves override → parameter default → 0
|
||||
# Effective target - resolves override → parameter default → 0
|
||||
# ------------------------------------------------------------------
|
||||
effective_target = fields.Float(
|
||||
string='Effective Setpoint',
|
||||
@@ -253,7 +253,7 @@ class FpTankSensor(models.Model):
|
||||
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
|
||||
# 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',
|
||||
@@ -308,7 +308,7 @@ class FpTankSensor(models.Model):
|
||||
rec.reading_count = len(rec.reading_ids)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Resolve effective alert range — override wins, else bath.parameter
|
||||
# Resolve effective alert range - override wins, else bath.parameter
|
||||
# ------------------------------------------------------------------
|
||||
def _get_alert_range(self):
|
||||
"""Return (min, max) floats. Zero means 'no bound'."""
|
||||
@@ -324,7 +324,7 @@ class FpTankSensor(models.Model):
|
||||
def _get_setpoint(self):
|
||||
"""Canonical (raw) setpoint used for deviation calcs.
|
||||
|
||||
Returns 0.0 if no setpoint is set at either level — callers should
|
||||
Returns 0.0 if no setpoint is set at either level - callers should
|
||||
treat 0 as "no target defined, skip deviation display".
|
||||
"""
|
||||
self.ensure_one()
|
||||
@@ -336,7 +336,7 @@ class FpTankSensor(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': f'Readings — {self.name}',
|
||||
'name': f'Readings - {self.name}',
|
||||
'res_model': 'fp.tank.reading',
|
||||
'view_mode': 'list,form,graph',
|
||||
'domain': [('sensor_id', '=', self.id)],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 7 — company-wide default for the IoT sensor polling interval.
|
||||
# Sub 7 - company-wide default for the IoT sensor polling interval.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
#
|
||||
# Sub 7 — Expose the IoT polling default on the Fusion Plating
|
||||
# Sub 7 - Expose the IoT polling default on the Fusion Plating
|
||||
# Settings page so admins manage it alongside other plating settings.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
invisible="last_reading_in_spec or not last_reading_at"
|
||||
bg_color="text-bg-danger"/>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" placeholder="e.g. Tank 3 — ENP temp"/></h1>
|
||||
<h1><field name="name" placeholder="e.g. Tank 3 - ENP temp"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Identity">
|
||||
@@ -73,10 +73,10 @@
|
||||
<field name="device_serial" placeholder="28-abc123def456"/>
|
||||
<field name="iot_device_id"
|
||||
options="{'no_create': True}"
|
||||
help="Optional — the iot.device auto-registered by the Pi proxy."/>
|
||||
help="Optional - the iot.device auto-registered by the Pi proxy."/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Location — fill in ONE (first one set wins for display)">
|
||||
<group string="Location - fill in ONE (first one set wins for display)">
|
||||
<group>
|
||||
<field name="tank_id" options="{'no_create': True}"/>
|
||||
<field name="bath_id" options="{'no_create': True}"/>
|
||||
@@ -91,7 +91,7 @@
|
||||
<group string="Setpoint & Alerting">
|
||||
<group string="Target (setpoint)">
|
||||
<field name="target_value_override"
|
||||
help="Leave 0 to inherit from bath parameter's Default Setpoint. This is the IDEAL operating value — not an alarm threshold."/>
|
||||
help="Leave 0 to inherit from bath parameter's Default Setpoint. This is the IDEAL operating value - not an alarm threshold."/>
|
||||
<field name="effective_target" readonly="1"/>
|
||||
<field name="effective_target_unit" readonly="1"/>
|
||||
</group>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1 (Odoo Proprietary License v1.0)
|
||||
Part of the Fusion Plating product family.
|
||||
Sub 7 — IoT default polling interval setting.
|
||||
Sub 7 - IoT default polling interval setting.
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user