# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. from datetime import timedelta from odoo import api, fields, models class FpCalibrationEquipment(models.Model): """Equipment master for the calibration register. Holds the metadata about each measuring instrument the shop owns: type, NIST traceability, calibration interval, and computed next-due date. Individual events live on fusion.plating.calibration.event. """ _name = 'fusion.plating.calibration.equipment' _description = 'Fusion Plating — Calibrated Equipment' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'next_cal_date asc, name' name = fields.Char( string='Equipment', required=True, tracking=True, ) code = fields.Char( string='Asset Code', required=True, tracking=True, ) equipment_type = fields.Selection( [ ('xrf', 'XRF Analyzer'), ('ph_meter', 'pH Meter'), ('thermocouple', 'Thermocouple'), ('balance', 'Balance / Scale'), ('timer', 'Timer'), ('thickness_gauge', 'Thickness Gauge'), ('multimeter', 'Multimeter'), ('other', 'Other'), ], string='Type', default='other', tracking=True, ) facility_id = fields.Many2one( 'fusion.plating.facility', string='Facility', tracking=True, ) company_id = fields.Many2one( 'res.company', related='facility_id.company_id', store=True, readonly=True, ) nist_traceable = fields.Boolean( string='NIST Traceable', default=True, ) calibration_interval_days = fields.Integer( string='Interval (days)', default=365, help='Number of days between calibrations.', ) last_cal_date = fields.Date( string='Last Calibration', compute='_compute_cal_dates', store=True, ) next_cal_date = fields.Date( string='Next Calibration', compute='_compute_cal_dates', store=True, ) state = fields.Selection( [ ('in_service', 'In Service'), ('due_soon', 'Due Soon'), ('overdue', 'Overdue'), ('out_of_service', 'Out of Service'), ], string='Status', compute='_compute_state', store=True, tracking=True, ) manual_state = fields.Selection( [ ('in_service', 'In Service'), ('out_of_service', 'Out of Service'), ], string='Manual Override', default='in_service', help='Use this to mark a unit out-of-service even if it is not yet ' 'overdue (e.g. damaged in transit).', tracking=True, ) active = fields.Boolean(default=True) event_ids = fields.One2many( 'fusion.plating.calibration.event', 'equipment_id', string='Calibration Events', ) event_count = fields.Integer( compute='_compute_event_count', ) _sql_constraints = [ ( 'fp_cal_equipment_code_uniq', 'unique(code, company_id)', 'Asset code must be unique per company.', ), ] @api.depends('event_ids', 'event_ids.cal_date', 'calibration_interval_days') def _compute_cal_dates(self): for rec in self: last_event = rec.event_ids.sorted('cal_date', reverse=True)[:1] rec.last_cal_date = last_event.cal_date if last_event else False if rec.last_cal_date and rec.calibration_interval_days: rec.next_cal_date = rec.last_cal_date + timedelta( days=rec.calibration_interval_days ) else: rec.next_cal_date = False @api.depends('next_cal_date', 'manual_state') def _compute_state(self): today = fields.Date.context_today(self) for rec in self: if rec.manual_state == 'out_of_service': rec.state = 'out_of_service' continue if not rec.next_cal_date: rec.state = 'in_service' continue days_left = (rec.next_cal_date - today).days if days_left < 0: rec.state = 'overdue' elif days_left <= 14: rec.state = 'due_soon' else: rec.state = 'in_service' @api.depends('event_ids') def _compute_event_count(self): for rec in self: rec.event_count = len(rec.event_ids) def action_mark_out_of_service(self): self.write({'manual_state': 'out_of_service'}) def action_return_to_service(self): self.write({'manual_state': 'in_service'}) def action_view_events(self): self.ensure_one() return { 'name': 'Calibration Events', 'type': 'ir.actions.act_window', 'res_model': 'fusion.plating.calibration.event', 'view_mode': 'list,form', 'domain': [('equipment_id', '=', self.id)], 'context': {'default_equipment_id': self.id}, }