# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. from odoo import api, fields, models class FpBathLogLine(models.Model): """A single parameter reading on a bath log. Each line = one titration result or one sensor reading. Target ranges are pulled from the bath's per-bath overrides if present, otherwise from the parameter's defaults on fusion.plating.bath.parameter. Status is computed per line (ok / warning / out_of_spec) and rolled up to the parent log. """ _name = 'fusion.plating.bath.log.line' _description = 'Fusion Plating — Bath Log Reading' _order = 'log_id, sequence, id' log_id = fields.Many2one( 'fusion.plating.bath.log', string='Log', required=True, ondelete='cascade', index=True, ) bath_id = fields.Many2one( related='log_id.bath_id', store=True, readonly=True, ) sequence = fields.Integer( string='Sequence', default=10, ) parameter_id = fields.Many2one( 'fusion.plating.bath.parameter', string='Parameter', required=True, ondelete='restrict', ) parameter_code = fields.Char( related='parameter_id.code', store=True, readonly=True, ) uom = fields.Char( related='parameter_id.uom', readonly=True, ) value = fields.Float( string='Value', required=True, ) target_min = fields.Float( string='Target Min', compute='_compute_targets', store=True, ) target_max = fields.Float( string='Target Max', compute='_compute_targets', store=True, ) status = fields.Selection( [ ('ok', 'OK'), ('warning', 'Warning'), ('out_of_spec', 'Out of Spec'), ], string='Status', compute='_compute_status', store=True, ) notes = fields.Char( string='Notes', ) # ========================================================================== @api.depends('parameter_id', 'log_id.bath_id') def _compute_targets(self): """Resolve target range: per-bath override first, parameter default second.""" for rec in self: tmin = tmax = 0.0 if rec.log_id.bath_id and rec.parameter_id: override = rec.log_id.bath_id.target_line_ids.filtered( lambda t: t.parameter_id.id == rec.parameter_id.id )[:1] if override: tmin, tmax = override.target_min, override.target_max else: tmin = rec.parameter_id.target_min tmax = rec.parameter_id.target_max rec.target_min = tmin rec.target_max = tmax @api.depends('value', 'target_min', 'target_max', 'parameter_id.warning_tolerance') def _compute_status(self): for rec in self: if rec.target_min == 0.0 and rec.target_max == 0.0: rec.status = 'ok' continue v, lo, hi = rec.value, rec.target_min, rec.target_max if v < lo or v > hi: rec.status = 'out_of_spec' continue tol_pct = (rec.parameter_id.warning_tolerance or 0.0) / 100.0 span = max(hi - lo, 1e-9) if tol_pct > 0 and (v - lo < span * tol_pct or hi - v < span * tol_pct): rec.status = 'warning' else: rec.status = 'ok'