Files
2026-04-16 20:53:53 -04:00

172 lines
5.2 KiB
Python

# -*- 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},
}