# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. # # Phase 1 (Sub 11) — relocated from fusion_plating_bridge_mrp. # This model never had MRP fields; the bridge module was just its # initial home. Now lives under fusion_plating_jobs. """QC Checklist Template — admin config for per-customer QC requirements. Customers differ wildly in what they expect from quality control: * commercial job-shop accounts often just want "did it plate?" — one visual check * aerospace / Nadcap customers expect visual, dimensional, adhesion, and Fischerscope thickness readings — every part, every lot, signed off * internal rework jobs may have no QC requirement at all Rather than coding that policy into the shop, each customer gets their own checklist template. On job confirm, the active template is cloned into a fresh `fusion.plating.quality.check` — the instance operators actually fill in. """ from odoo import api, fields, models, _ class FpQcChecklistTemplate(models.Model): _name = 'fp.qc.checklist.template' _description = 'Fusion Plating — QC Checklist Template' _inherit = ['mail.thread'] _order = 'partner_id, sequence, name' name = fields.Char( string='Template Name', required=True, tracking=True, help='e.g. "Standard Aerospace CoC + Thickness" or ' '"Commercial — Visual Only".', ) sequence = fields.Integer(default=10) active = fields.Boolean(default=True) partner_id = fields.Many2one( 'res.partner', string='Customer', domain="[('customer_rank', '>', 0)]", help='Leave blank for the global default template. A customer-' 'specific template wins over the default when both exist.', tracking=True, ) notes = fields.Html( string='Notes', help='Context for QC inspectors — what this customer cares ' 'about, common reject reasons, spec docs to reference.', ) line_ids = fields.One2many( 'fp.qc.checklist.template.line', 'template_id', string='Checklist Items', copy=True, ) require_thickness_readings = fields.Boolean( string='Require Thickness Readings', default=False, tracking=True, help='Job cannot be marked done unless at least one ' 'fp.thickness.reading is logged against it. Use for ' 'aerospace / Nadcap accounts.', ) require_thickness_report_pdf = fields.Boolean( string='Require Thickness Report PDF', default=False, tracking=True, help='Job cannot be marked done unless the operator has ' 'uploaded the Fischerscope / XDAL 600 PDF report to the ' 'quality check.', ) require_inspector_signoff = fields.Boolean( string='Require Inspector Sign-off', default=True, tracking=True, help='The quality check itself must be in the "passed" state ' '(not just draft or in-progress).', ) check_count = fields.Integer( string='# QC Checks Created', compute='_compute_check_count', ) def _compute_check_count(self): Check = self.env['fusion.plating.quality.check'] for rec in self: rec.check_count = Check.search_count([ ('template_id', '=', rec.id), ]) @api.model def resolve_for_partner(self, partner): """Return the best-matching template for a customer. Order: active customer-specific template > active default template > None (no QC required). """ if partner: specific = self.search([ ('partner_id', '=', partner.id), ('active', '=', True), ], limit=1) if specific: return specific return self.search([ ('partner_id', '=', False), ('active', '=', True), ], limit=1) def action_view_checks(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('QC Checks — %s') % self.name, 'res_model': 'fusion.plating.quality.check', 'view_mode': 'list,form', 'domain': [('template_id', '=', self.id)], 'target': 'current', } class FpQcChecklistTemplateLine(models.Model): _name = 'fp.qc.checklist.template.line' _description = 'Fusion Plating — QC Checklist Template Line' _order = 'sequence, id' template_id = fields.Many2one( 'fp.qc.checklist.template', string='Template', required=True, ondelete='cascade', ) sequence = fields.Integer(default=10) name = fields.Char( string='Check Item', required=True, translate=True, help='The operator-facing question, e.g. "No visible pits or ' 'blemishes on surface", "Thickness within 0.0005–0.0010".', ) description = fields.Text( string='Inspection Guidance', help='Extra detail shown on the tablet when the operator taps ' 'the item. Use for photos-to-compare-against, acceptable-' 'colour ranges, how to position the part, etc.', ) check_type = fields.Selection( [ ('visual', 'Visual Inspection'), ('dimensional', 'Dimensional'), ('thickness', 'Thickness'), ('adhesion', 'Adhesion'), ('hardness', 'Hardness'), ('salt_spray', 'Salt Spray'), ('functional', 'Functional'), ('other', 'Other'), ], string='Check Type', default='visual', required=True, ) required = fields.Boolean( string='Required', default=True, help='If off, the inspector can skip this item without blocking ' 'the QC from passing.', ) requires_value = fields.Boolean( string='Requires Numeric Value', default=False, help='Inspector must enter a measurement. If min/max are set, ' 'the reading must fall inside to count as pass.', ) value_min = fields.Float(string='Min Value', digits=(12, 4)) value_max = fields.Float(string='Max Value', digits=(12, 4)) value_uom = fields.Char( string='Unit', help='Free text. e.g. "mils", "microns", "HV", "µm".', ) requires_photo = fields.Boolean( string='Requires Photo', default=False, help='Inspector must attach a photo of the part.', )