# -*- 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 FpBatch(models.Model): """A rack or barrel load of parts being processed through a tank. Lifecycle: draft → loading → in_process → unloading → complete ↗ (any non-complete state) → cancelled """ _name = 'fusion.plating.batch' _description = 'Plating Batch (Rack/Barrel Load)' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'create_date desc' name = fields.Char( string='Batch Reference', required=True, copy=False, readonly=True, default=lambda self: self.env['ir.sequence'].next_by_code( 'fusion.plating.batch') or '/', tracking=True, ) facility_id = fields.Many2one( 'fusion.plating.facility', string='Facility', required=True, tracking=True, ) bath_id = fields.Many2one( 'fusion.plating.bath', string='Bath', required=True, tracking=True, ) tank_id = fields.Many2one( 'fusion.plating.tank', string='Tank', related='bath_id.tank_id', store=True, readonly=True, ) process_type_id = fields.Many2one( related='bath_id.process_type_id', store=True, readonly=True, ) state = fields.Selection( selection=[ ('draft', 'Draft'), ('loading', 'Loading'), ('in_process', 'In Process'), ('unloading', 'Unloading'), ('complete', 'Complete'), ('cancelled', 'Cancelled'), ], string='Status', default='draft', required=True, tracking=True, ) rack_ref = fields.Char(string='Rack / Barrel Ref (legacy)') rack_id = fields.Many2one( 'fusion.plating.rack', string='Rack / Fixture', domain="[('state', '!=', 'retired')]", tracking=True, ) # Phase 6 (Sub 11) — workorder_id / production_id retired (MRP gone). # Native equivalents: x_fc_step_id (fp.job.step) + x_fc_job_id (fp.job) # are added by fusion_plating_jobs and carry the same traceability. part_count = fields.Integer(string='Part Count') start_time = fields.Datetime(string='Process Start', tracking=True) end_time = fields.Datetime(string='Process End', tracking=True) duration_minutes = fields.Float( string='Duration (min)', compute='_compute_duration', store=True, ) chemistry_ids = fields.One2many( 'fusion.plating.batch.chemistry', 'batch_id', string='Chemistry Readings', ) chemistry_count = fields.Integer( string='Readings', compute='_compute_chemistry_count', ) operator_id = fields.Many2one( 'res.users', string='Operator', default=lambda self: self.env.user, tracking=True, ) company_id = fields.Many2one( 'res.company', string='Company', default=lambda self: self.env.company, ) notes = fields.Html(string='Notes') active = fields.Boolean(default=True) # ------------------------------------------------------------------------- # Compute # ------------------------------------------------------------------------- @api.depends('start_time', 'end_time') def _compute_duration(self): for rec in self: if rec.start_time and rec.end_time: delta = rec.end_time - rec.start_time rec.duration_minutes = delta.total_seconds() / 60.0 else: rec.duration_minutes = 0.0 @api.depends('chemistry_ids') def _compute_chemistry_count(self): for rec in self: rec.chemistry_count = len(rec.chemistry_ids) # ------------------------------------------------------------------------- # Actions # ------------------------------------------------------------------------- def action_start_loading(self): self.write({'state': 'loading'}) def action_start_process(self): self.write({ 'state': 'in_process', 'start_time': fields.Datetime.now(), }) def action_start_unloading(self): self.write({ 'state': 'unloading', 'end_time': fields.Datetime.now(), }) def action_complete(self): self.write({'state': 'complete'}) def action_cancel(self): self.write({'state': 'cancelled'})