# -*- 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 FpCapa(models.Model): """Corrective and Preventive Action. A CAPA carries an issue from "we found a problem" all the way to "we proved the fix worked". Each CAPA has an owner, a due date, an action plan, and an effectiveness verification step. """ _name = 'fusion.plating.capa' _description = 'Fusion Plating — Corrective / Preventive Action' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'due_date asc, id desc' name = fields.Char( string='Reference', required=True, copy=False, readonly=True, default=lambda self: self._default_name(), tracking=True, ) state = fields.Selection( [ ('draft', 'Draft'), ('analysis', 'Analysis'), ('implementation', 'Implementation'), ('verification', 'Verification'), ('effective', 'Effective'), ('not_effective', 'Not Effective'), ('closed', 'Closed'), ], string='Status', default='draft', required=True, tracking=True, ) type = fields.Selection( [ ('corrective', 'Corrective'), ('preventive', 'Preventive'), ], string='Type', default='corrective', required=True, tracking=True, ) ncr_id = fields.Many2one( 'fusion.plating.ncr', string='Source NCR', ondelete='set null', tracking=True, ) facility_id = fields.Many2one( 'fusion.plating.facility', string='Facility', related='ncr_id.facility_id', store=True, readonly=False, ) company_id = fields.Many2one( 'res.company', related='facility_id.company_id', store=True, readonly=True, ) description = fields.Html(string='Description') root_cause_analysis = fields.Html( string='Root Cause Analysis', help='Use 5 Whys, fishbone, or any structured method.', ) action_plan = fields.Html(string='Action Plan') owner_id = fields.Many2one( 'res.users', string='Owner', required=True, default=lambda self: self.env.user, tracking=True, ) due_date = fields.Date(string='Due Date', tracking=True) verification_date = fields.Date(string='Verification Date', tracking=True) verification_by_id = fields.Many2one( 'res.users', string='Verified By', tracking=True, ) is_effective = fields.Boolean(string='Effective', tracking=True) effectiveness_notes = fields.Html(string='Effectiveness Notes') is_overdue = fields.Boolean( string='Overdue', compute='_compute_is_overdue', search='_search_is_overdue', ) active = fields.Boolean(default=True) @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.capa') return seq or '/' @api.model_create_multi def create(self, vals_list): for vals in vals_list: if not vals.get('name') or vals.get('name') == '/': vals['name'] = self._default_name() return super().create(vals_list) @api.depends('due_date', 'state') def _compute_is_overdue(self): today = fields.Date.context_today(self) for rec in self: rec.is_overdue = bool( rec.due_date and rec.state not in ('effective', 'closed') and rec.due_date < today ) def _search_is_overdue(self, operator, value): today = fields.Date.context_today(self) if (operator == '=' and value) or (operator == '!=' and not value): return [ ('due_date', '<', today), ('state', 'not in', ['effective', 'closed']), ] return [ '|', ('due_date', '>=', today), ('state', 'in', ['effective', 'closed']), ] def action_start_analysis(self): self.write({'state': 'analysis'}) def action_start_implementation(self): self.write({'state': 'implementation'}) def action_start_verification(self): self.write({'state': 'verification'}) def action_mark_effective(self): self.write({ 'state': 'effective', 'is_effective': True, 'verification_date': fields.Date.context_today(self), 'verification_by_id': self.env.user.id, }) def action_mark_not_effective(self): self.write({ 'state': 'not_effective', 'is_effective': False, 'verification_date': fields.Date.context_today(self), 'verification_by_id': self.env.user.id, }) def action_close(self): self.write({'state': 'closed'}) def action_reset_to_draft(self): self.write({'state': 'draft'})