172 lines
5.2 KiB
Python
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},
|
|
}
|