# -*- 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. 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 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, pH → pH probe, etc.). """ import logging import uuid as _uuid _logger = logging.getLogger(__name__) def post_init_hook(env): _backfill_uuids(env) _backfill_sensor_types(env) _seed_entech_tanks_and_sensors(env) def _backfill_uuids(env): Sensor = env['fp.tank.sensor'] missing = Sensor.search([('uuid', '=', False)]) if not missing: return for s in missing: s.sudo().write({'uuid': _uuid.uuid4().hex}) _logger.info('fp.tank.sensor: populated UUID on %d existing records', len(missing)) def _backfill_sensor_types(env): Sensor = env['fp.tank.sensor'] Type = env['fp.sensor.type'] # Map device_kind → sensor-type code. Falls back to 'temperature' for # the rare case someone set device_kind='other' on a probe that IS # temperature (common on the pilot). kind_to_code = { 'ds18b20': 'temperature', 'pt100': 'temperature', 'pt1000': 'temperature', 'ph': 'ph', 'conductivity': 'conductivity', 'level': 'level', } missing = Sensor.search([('sensor_type_id', '=', False)]) if not missing: return # Resolve the types once up front type_cache = {} for code in set(kind_to_code.values()): t = Type.search([('code', '=', code)], limit=1) if t: type_cache[code] = t.id updated = 0 for s in missing: code = kind_to_code.get(s.device_kind) # Unmapped (device_kind='other') → try temperature as the most # common fallback. Admin can correct in the UI. type_id = type_cache.get(code) or type_cache.get('temperature') if type_id: s.sudo().write({'sensor_type_id': type_id}) updated += 1 _logger.info('fp.tank.sensor: set default sensor_type_id on %d records', updated) def _seed_entech_tanks_and_sensors(env): """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. Sensors keyed by (tank_id, parameter_type) so duplicates aren't created if the admin added a temp/pH sensor manually after seed. Opt-in via system parameter `fusion_plating_iot.seed_entech_tanks`. Default False so a fresh install doesn't auto-seed on anyone else. Admin sets it to '1' via Settings → Technical → System Parameters and re-runs the module upgrade (-u fusion_plating_iot) to trigger. """ Param = env['ir.config_parameter'].sudo() if Param.get_param('fusion_plating_iot.seed_entech_tanks', '0') != '1': return # with_context(active_test=False) so the idempotency search sees # the inactive big tanks and doesn't try to recreate them on re-run. Tank = env['fusion.plating.tank'].with_context(active_test=False) Sensor = env['fp.tank.sensor'].with_context(active_test=False) Parameter = env['fusion.plating.bath.parameter'] Facility = env['fusion.plating.facility'] facility = Facility.search([], limit=1) if not facility: _logger.warning('Sub 7 seed: no fusion.plating.facility found — ' 'skipping tank seed. Create a facility first.') return temp_param = ( Parameter.search([('code', '=', 'TEMP')], limit=1) or Parameter.search([('parameter_type', '=', 'temperature')], limit=1) ) ph_param = ( Parameter.search([('code', '=', 'PH')], limit=1) or Parameter.search([('parameter_type', '=', 'ph')], limit=1) ) if not temp_param or not ph_param: _logger.warning('Sub 7 seed: temperature / pH bath parameters ' 'not found — skipping. Seed bath parameters first.') return plan = [] for i in range(1, 6): plan.append({'name': 'Small Tank #%d' % i, 'code': 'SMALL-%02d' % i, 'active': True, 'sequence': 10 + i}) for i in range(1, 21): plan.append({'name': 'Big Tank #%d' % i, 'code': 'BIG-%02d' % i, 'active': (i <= 10), 'sequence': 20 + i}) tanks_created = 0 sensors_created = 0 for row in plan: tank = Tank.search([('code', '=', row['code'])], limit=1) if not tank: tank = Tank.create({ 'name': row['name'], 'code': row['code'], 'facility_id': facility.id, 'sequence': row['sequence'], 'active': row['active'], }) tanks_created += 1 # Temperature sensor if not Sensor.search_count([ ('tank_id', '=', tank.id), ('parameter_id.parameter_type', '=', 'temperature'), ]): Sensor.create({ 'name': '%s — Temperature' % tank.name, 'tank_id': tank.id, 'parameter_id': temp_param.id, 'device_kind': 'ds18b20', 'active': row['active'], }) sensors_created += 1 # pH sensor if not Sensor.search_count([ ('tank_id', '=', tank.id), ('parameter_id.parameter_type', '=', 'ph'), ]): Sensor.create({ 'name': '%s — pH' % tank.name, 'tank_id': tank.id, 'parameter_id': ph_param.id, 'device_kind': 'ph', 'active': row['active'], }) sensors_created += 1 _logger.info('Sub 7 seed: created %d tanks + %d sensors', tanks_created, sensors_created)