# -*- 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 FpNcr(models.Model): """Non-Conformance Report. The NCR is the entry point of the Fusion Plating QMS. Anything that falls outside of spec — a chemistry deviation, a customer return, an inspection failure, an audit observation — is opened as an NCR and walked through containment, disposition, and closure. """ _name = 'fusion.plating.ncr' _description = 'Fusion Plating — Non-Conformance Report' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'reported_date desc, 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'), ('open', 'Open'), ('containment', 'Containment'), ('disposition', 'Disposition'), ('closed', 'Closed'), ], string='Status', default='draft', required=True, tracking=True, ) facility_id = fields.Many2one( 'fusion.plating.facility', string='Facility', required=True, tracking=True, ) company_id = fields.Many2one( 'res.company', related='facility_id.company_id', store=True, readonly=True, ) reported_by_id = fields.Many2one( 'res.users', string='Reported By', default=lambda self: self.env.user, tracking=True, ) reported_date = fields.Datetime( string='Reported On', default=lambda self: fields.Datetime.now(), tracking=True, ) closed_date = fields.Datetime( string='Closed On', readonly=True, tracking=True, ) source = fields.Selection( [ ('shop_floor', 'Shop Floor'), ('inspection', 'Inspection'), ('customer', 'Customer'), ('audit', 'Audit'), ('supplier', 'Supplier'), ('other', 'Other'), ], string='Source', default='shop_floor', tracking=True, ) severity = fields.Selection( [ ('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical'), ], string='Severity', default='medium', tracking=True, ) part_ref = fields.Char(string='Part / Lot') quantity_affected = fields.Float(string='Quantity Affected') description = fields.Html(string='Description') root_cause = fields.Html(string='Root Cause') containment = fields.Html(string='Containment Actions') disposition = fields.Selection( [ ('use_as_is', 'Use as Is'), ('rework', 'Rework'), ('scrap', 'Scrap'), ('return_to_customer', 'Return to Customer'), ('pending', 'Pending'), ], string='Disposition', default='pending', tracking=True, ) bath_id = fields.Many2one( 'fusion.plating.bath', string='Bath', help='If the non-conformance was caused by a specific chemistry bath.', ) customer_partner_id = fields.Many2one( 'res.partner', string='Customer', ) capa_ids = fields.One2many( 'fusion.plating.capa', 'ncr_id', string='CAPAs', ) capa_count = fields.Integer( string='# CAPAs', compute='_compute_capa_count', ) active = fields.Boolean(default=True) @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.ncr') 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('capa_ids') def _compute_capa_count(self): for rec in self: rec.capa_count = len(rec.capa_ids) def action_open(self): self.write({'state': 'open'}) def action_containment(self): self.write({'state': 'containment'}) def action_disposition(self): self.write({'state': 'disposition'}) def action_close(self): self.write({ 'state': 'closed', 'closed_date': fields.Datetime.now(), }) def action_reset_to_draft(self): self.write({'state': 'draft', 'closed_date': False}) def action_view_capas(self): self.ensure_one() return { 'name': 'CAPAs', 'type': 'ir.actions.act_window', 'res_model': 'fusion.plating.capa', 'view_mode': 'list,form', 'domain': [('ncr_id', '=', self.id)], 'context': {'default_ncr_id': self.id}, }