changes
This commit is contained in:
269
fusion-plating/fusion_plating/models/fp_bath.py
Normal file
269
fusion-plating/fusion_plating/models/fp_bath.py
Normal file
@@ -0,0 +1,269 @@
|
||||
# -*- 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 FpBath(models.Model):
|
||||
"""A specific batch of chemistry in a tank.
|
||||
|
||||
Baths have their own lifecycle independent of the tank:
|
||||
|
||||
new → operational → under_review → dump_scheduled → dumped
|
||||
|
||||
Each bath carries:
|
||||
* its process type (which chemistry it runs)
|
||||
* per-bath target ranges (may override process defaults)
|
||||
* running MTO counter (set and maintained by the process pack)
|
||||
* chemistry log history (one2many to fusion.plating.bath.log)
|
||||
|
||||
Process packs (fusion_plating_process_en, etc.) add process-specific
|
||||
computed fields such as orthophosphite projection or P-content band
|
||||
without touching the generic bath model.
|
||||
"""
|
||||
_name = 'fusion.plating.bath'
|
||||
_description = 'Fusion Plating — Bath'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'state, makeup_date desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
|
||||
name = fields.Char(
|
||||
string='Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
default=lambda self: self._default_name(),
|
||||
tracking=True,
|
||||
)
|
||||
display_name = fields.Char(
|
||||
compute='_compute_display_name',
|
||||
store=True,
|
||||
)
|
||||
tank_id = fields.Many2one(
|
||||
'fusion.plating.tank',
|
||||
string='Tank',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
tracking=True,
|
||||
)
|
||||
facility_id = fields.Many2one(
|
||||
'fusion.plating.facility',
|
||||
related='tank_id.facility_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
process_type_id = fields.Many2one(
|
||||
'fusion.plating.process.type',
|
||||
string='Process',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
tracking=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company',
|
||||
related='facility_id.company_id',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
# ----- Lifecycle ------------------------------------------------------
|
||||
state = fields.Selection(
|
||||
[
|
||||
('new', 'New'),
|
||||
('operational', 'Operational'),
|
||||
('under_review', 'Under Review'),
|
||||
('dump_scheduled', 'Dump Scheduled'),
|
||||
('dumped', 'Dumped'),
|
||||
],
|
||||
string='Status',
|
||||
default='new',
|
||||
tracking=True,
|
||||
required=True,
|
||||
)
|
||||
status_color = fields.Integer(
|
||||
string='Status Color',
|
||||
compute='_compute_status_color',
|
||||
help='Kanban colour index derived from state and chemistry health.',
|
||||
)
|
||||
makeup_date = fields.Datetime(
|
||||
string='Makeup Date',
|
||||
help='When this bath was made up (initial fresh charge).',
|
||||
tracking=True,
|
||||
)
|
||||
makeup_by_id = fields.Many2one(
|
||||
'res.users',
|
||||
string='Made Up By',
|
||||
tracking=True,
|
||||
)
|
||||
dump_scheduled_date = fields.Datetime(
|
||||
string='Dump Scheduled',
|
||||
tracking=True,
|
||||
)
|
||||
dumped_date = fields.Datetime(
|
||||
string='Dumped Date',
|
||||
tracking=True,
|
||||
)
|
||||
dump_reason = fields.Text(
|
||||
string='Dump Reason',
|
||||
)
|
||||
notes = fields.Html(
|
||||
string='Notes',
|
||||
)
|
||||
|
||||
# ----- Chemistry target ranges (per-bath; override process defaults) --
|
||||
target_line_ids = fields.One2many(
|
||||
'fusion.plating.bath.target',
|
||||
'bath_id',
|
||||
string='Target Parameters',
|
||||
copy=True,
|
||||
)
|
||||
|
||||
# ----- Logs -----------------------------------------------------------
|
||||
log_ids = fields.One2many(
|
||||
'fusion.plating.bath.log',
|
||||
'bath_id',
|
||||
string='Chemistry Logs',
|
||||
)
|
||||
log_count = fields.Integer(
|
||||
compute='_compute_log_count',
|
||||
)
|
||||
last_log_date = fields.Datetime(
|
||||
compute='_compute_last_log',
|
||||
store=True,
|
||||
)
|
||||
last_log_status = fields.Selection(
|
||||
[
|
||||
('ok', 'OK'),
|
||||
('warning', 'Warning'),
|
||||
('out_of_spec', 'Out of Spec'),
|
||||
],
|
||||
compute='_compute_last_log',
|
||||
store=True,
|
||||
)
|
||||
|
||||
# ----- Generic age / volume (process packs refine) --------------------
|
||||
mto_count = fields.Float(
|
||||
string='MTO',
|
||||
default=0.0,
|
||||
help='Metal Turnovers. Maintained by process packs that model '
|
||||
'replenishment (e.g. fusion_plating_process_en).',
|
||||
)
|
||||
volume = fields.Float(
|
||||
string='Volume',
|
||||
help='Working volume (defaults to tank volume on makeup).',
|
||||
)
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# ==========================================================================
|
||||
# Defaults
|
||||
# ==========================================================================
|
||||
@api.model
|
||||
def _default_name(self):
|
||||
seq = self.env['ir.sequence'].next_by_code('fusion.plating.bath')
|
||||
return seq or '/'
|
||||
|
||||
# ==========================================================================
|
||||
# Computes
|
||||
# ==========================================================================
|
||||
@api.depends('name', 'process_type_id', 'tank_id')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
parts = [rec.name or '']
|
||||
if rec.process_type_id:
|
||||
parts.append(f'({rec.process_type_id.code})')
|
||||
if rec.tank_id:
|
||||
parts.append(f'@ {rec.tank_id.code}')
|
||||
rec.display_name = ' '.join(p for p in parts if p)
|
||||
|
||||
def _compute_log_count(self):
|
||||
for rec in self:
|
||||
rec.log_count = len(rec.log_ids)
|
||||
|
||||
@api.depends('log_ids', 'log_ids.log_date', 'log_ids.status')
|
||||
def _compute_last_log(self):
|
||||
for rec in self:
|
||||
last = rec.log_ids.sorted('log_date', reverse=True)[:1]
|
||||
rec.last_log_date = last.log_date if last else False
|
||||
rec.last_log_status = last.status if last else False
|
||||
|
||||
@api.depends('state', 'last_log_status')
|
||||
def _compute_status_color(self):
|
||||
"""Kanban colour index — neutral palette that works in light + dark.
|
||||
|
||||
Uses Odoo's built-in color index rather than hex codes, so themes
|
||||
control the final rendering.
|
||||
"""
|
||||
# 0=no color, 4=green, 3=yellow, 2=orange, 1=red, 5=purple, 10=grey
|
||||
for rec in self:
|
||||
if rec.state == 'dumped':
|
||||
rec.status_color = 10 # grey
|
||||
elif rec.state == 'dump_scheduled':
|
||||
rec.status_color = 2 # orange
|
||||
elif rec.state == 'under_review':
|
||||
rec.status_color = 3 # yellow
|
||||
elif rec.state == 'new':
|
||||
rec.status_color = 5 # purple
|
||||
elif rec.last_log_status == 'out_of_spec':
|
||||
rec.status_color = 1 # red
|
||||
elif rec.last_log_status == 'warning':
|
||||
rec.status_color = 3 # yellow
|
||||
else:
|
||||
rec.status_color = 4 # green
|
||||
|
||||
# ==========================================================================
|
||||
# Actions
|
||||
# ==========================================================================
|
||||
def action_make_operational(self):
|
||||
self.write({'state': 'operational'})
|
||||
|
||||
def action_mark_under_review(self):
|
||||
self.write({'state': 'under_review'})
|
||||
|
||||
def action_schedule_dump(self):
|
||||
self.write({
|
||||
'state': 'dump_scheduled',
|
||||
'dump_scheduled_date': fields.Datetime.now(),
|
||||
})
|
||||
|
||||
def action_dump(self):
|
||||
self.write({
|
||||
'state': 'dumped',
|
||||
'dumped_date': fields.Datetime.now(),
|
||||
})
|
||||
|
||||
|
||||
class FpBathTarget(models.Model):
|
||||
"""Per-bath target range for a chemistry parameter."""
|
||||
_name = 'fusion.plating.bath.target'
|
||||
_description = 'Fusion Plating — Bath Target'
|
||||
_order = 'bath_id, sequence, parameter_id'
|
||||
|
||||
bath_id = fields.Many2one(
|
||||
'fusion.plating.bath',
|
||||
string='Bath',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
parameter_id = fields.Many2one(
|
||||
'fusion.plating.bath.parameter',
|
||||
string='Parameter',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
)
|
||||
sequence = fields.Integer(default=10)
|
||||
target_min = fields.Float(string='Min')
|
||||
target_max = fields.Float(string='Max')
|
||||
uom = fields.Char(
|
||||
related='parameter_id.uom',
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
'fp_bath_target_uniq',
|
||||
'unique(bath_id, parameter_id)',
|
||||
'Each parameter can only be defined once per bath.',
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user